@linxiraos/pi-utils 1.0.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 (244) hide show
  1. package/CHANGELOG.md +449 -0
  2. package/README.md +36 -0
  3. package/dist/types/abortable.d.ts +32 -0
  4. package/dist/types/acp/connection.d.ts +118 -0
  5. package/dist/types/acp/protocol.d.ts +526 -0
  6. package/dist/types/acp/schema.d.ts +41 -0
  7. package/dist/types/acp/stream.d.ts +8 -0
  8. package/dist/types/acp/transport.d.ts +81 -0
  9. package/dist/types/acp.d.ts +6 -0
  10. package/dist/types/async.d.ts +20 -0
  11. package/dist/types/binary.d.ts +17 -0
  12. package/dist/types/browsers.d.ts +68 -0
  13. package/dist/types/chalk.d.ts +125 -0
  14. package/dist/types/cli.d.ts +134 -0
  15. package/dist/types/color.d.ts +102 -0
  16. package/dist/types/dates.d.ts +7 -0
  17. package/dist/types/dirs.d.ts +282 -0
  18. package/dist/types/docx/converter.d.ts +46 -0
  19. package/dist/types/docx/xml.d.ts +26 -0
  20. package/dist/types/docx/zip.d.ts +6 -0
  21. package/dist/types/docx.d.ts +11 -0
  22. package/dist/types/dom/core.d.ts +431 -0
  23. package/dist/types/dom/parser.d.ts +7 -0
  24. package/dist/types/dom/selector.d.ts +5 -0
  25. package/dist/types/dom.d.ts +5 -0
  26. package/dist/types/env.d.ts +124 -0
  27. package/dist/types/fetch-retry.d.ts +96 -0
  28. package/dist/types/file-lock.d.ts +23 -0
  29. package/dist/types/format.d.ts +37 -0
  30. package/dist/types/frontmatter.d.ts +46 -0
  31. package/dist/types/fs-error.d.ts +31 -0
  32. package/dist/types/glob.d.ts +28 -0
  33. package/dist/types/headers.d.ts +34 -0
  34. package/dist/types/index.d.ts +39 -0
  35. package/dist/types/json-parse.d.ts +74 -0
  36. package/dist/types/json.d.ts +14 -0
  37. package/dist/types/logger/rotating-file.d.ts +18 -0
  38. package/dist/types/logger.d.ts +96 -0
  39. package/dist/types/loop-phase.d.ts +10 -0
  40. package/dist/types/lru.d.ts +46 -0
  41. package/dist/types/marked/core.d.ts +445 -0
  42. package/dist/types/marked.d.ts +2 -0
  43. package/dist/types/mermaid-ascii.d.ts +11 -0
  44. package/dist/types/mime.d.ts +29 -0
  45. package/dist/types/module-timer.d.ts +1 -0
  46. package/dist/types/path-tree.d.ts +76 -0
  47. package/dist/types/path.d.ts +2 -0
  48. package/dist/types/peek-file.d.ts +29 -0
  49. package/dist/types/postmortem.d.ts +104 -0
  50. package/dist/types/process-name.d.ts +7 -0
  51. package/dist/types/procmgr.d.ts +74 -0
  52. package/dist/types/prompt.d.ts +18 -0
  53. package/dist/types/ptree.d.ts +110 -0
  54. package/dist/types/readability/readability.d.ts +9 -0
  55. package/dist/types/readability/readerable.d.ts +10 -0
  56. package/dist/types/readability/types.d.ts +70 -0
  57. package/dist/types/readability.d.ts +4 -0
  58. package/dist/types/ring.d.ts +93 -0
  59. package/dist/types/runtime-install.d.ts +78 -0
  60. package/dist/types/sanitize-text.d.ts +29 -0
  61. package/dist/types/snowflake.d.ts +25 -0
  62. package/dist/types/stderr-guard.d.ts +22 -0
  63. package/dist/types/stream.d.ts +67 -0
  64. package/dist/types/tab-spacing.d.ts +24 -0
  65. package/dist/types/temp.d.ts +17 -0
  66. package/dist/types/template.d.ts +62 -0
  67. package/dist/types/timing-buffer.d.ts +22 -0
  68. package/dist/types/tls-fetch.d.ts +37 -0
  69. package/dist/types/turndown/gfm.d.ts +11 -0
  70. package/dist/types/turndown/html.d.ts +5 -0
  71. package/dist/types/turndown/service.d.ts +21 -0
  72. package/dist/types/turndown/types.d.ts +70 -0
  73. package/dist/types/turndown.d.ts +4 -0
  74. package/dist/types/type-guards.d.ts +5 -0
  75. package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
  76. package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
  77. package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
  78. package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
  79. package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
  80. package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
  81. package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
  82. package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
  83. package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
  84. package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
  85. package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
  86. package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
  87. package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
  88. package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
  89. package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
  90. package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
  91. package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
  92. package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
  93. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
  94. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
  95. package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
  96. package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
  97. package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
  98. package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
  99. package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
  100. package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
  101. package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
  102. package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
  103. package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
  104. package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
  105. package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
  106. package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
  107. package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
  108. package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
  109. package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
  110. package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
  111. package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
  112. package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
  113. package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
  114. package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
  115. package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
  116. package/dist/types/version.d.ts +18 -0
  117. package/dist/types/vterm/buffer.d.ts +99 -0
  118. package/dist/types/vterm/terminal.d.ts +44 -0
  119. package/dist/types/vterm.d.ts +8 -0
  120. package/dist/types/which.d.ts +37 -0
  121. package/dist/types/worker-host.d.ts +51 -0
  122. package/dist/types/xml.d.ts +31 -0
  123. package/package.json +59 -0
  124. package/src/abortable.ts +109 -0
  125. package/src/acp/connection.ts +344 -0
  126. package/src/acp/protocol.ts +466 -0
  127. package/src/acp/schema.ts +160 -0
  128. package/src/acp/stream.ts +82 -0
  129. package/src/acp/transport.ts +213 -0
  130. package/src/acp.ts +6 -0
  131. package/src/async.ts +91 -0
  132. package/src/binary.ts +50 -0
  133. package/src/browsers.ts +501 -0
  134. package/src/chalk.ts +312 -0
  135. package/src/cli.ts +529 -0
  136. package/src/color.ts +302 -0
  137. package/src/dates.ts +194 -0
  138. package/src/dirs.ts +1026 -0
  139. package/src/docx/converter.ts +681 -0
  140. package/src/docx/xml.ts +166 -0
  141. package/src/docx/zip.ts +87 -0
  142. package/src/docx.ts +20 -0
  143. package/src/dom/core.ts +1254 -0
  144. package/src/dom/parser.ts +370 -0
  145. package/src/dom/selector.ts +290 -0
  146. package/src/dom.ts +33 -0
  147. package/src/env.ts +400 -0
  148. package/src/fetch-retry.ts +379 -0
  149. package/src/file-lock.ts +69 -0
  150. package/src/format.ts +113 -0
  151. package/src/frontmatter.ts +205 -0
  152. package/src/fs-error.ts +56 -0
  153. package/src/glob.ts +189 -0
  154. package/src/headers.ts +167 -0
  155. package/src/index.ts +59 -0
  156. package/src/json-parse.ts +821 -0
  157. package/src/json.ts +23 -0
  158. package/src/logger/rotating-file.ts +149 -0
  159. package/src/logger.ts +798 -0
  160. package/src/loop-phase.ts +49 -0
  161. package/src/lru.ts +185 -0
  162. package/src/marked/core.ts +1576 -0
  163. package/src/marked.ts +2 -0
  164. package/src/mermaid-ascii.ts +31 -0
  165. package/src/mime.ts +159 -0
  166. package/src/module-timer.ts +148 -0
  167. package/src/path-tree.ts +147 -0
  168. package/src/path.ts +28 -0
  169. package/src/peek-file.ts +188 -0
  170. package/src/postmortem.ts +433 -0
  171. package/src/process-name.ts +57 -0
  172. package/src/procmgr.ts +247 -0
  173. package/src/prompt.ts +545 -0
  174. package/src/ptree.ts +412 -0
  175. package/src/readability/readability.ts +533 -0
  176. package/src/readability/readerable.ts +51 -0
  177. package/src/readability/types.ts +72 -0
  178. package/src/readability.ts +11 -0
  179. package/src/ring.ts +169 -0
  180. package/src/runtime-install.ts +393 -0
  181. package/src/sanitize-text.ts +96 -0
  182. package/src/snowflake.ts +121 -0
  183. package/src/stderr-guard.ts +155 -0
  184. package/src/stream.ts +454 -0
  185. package/src/tab-spacing.ts +336 -0
  186. package/src/temp.ts +134 -0
  187. package/src/template.ts +586 -0
  188. package/src/timing-buffer.ts +47 -0
  189. package/src/tls-fetch.ts +178 -0
  190. package/src/turndown/gfm.ts +106 -0
  191. package/src/turndown/html.ts +257 -0
  192. package/src/turndown/service.ts +334 -0
  193. package/src/turndown/types.ts +81 -0
  194. package/src/turndown.ts +5 -0
  195. package/src/type-guards.ts +17 -0
  196. package/src/vendor/mermaid-ascii/NOTICE +33 -0
  197. package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
  198. package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
  199. package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
  200. package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
  201. package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
  202. package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
  203. package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
  204. package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
  205. package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
  206. package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
  207. package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
  208. package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +277 -0
  209. package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
  210. package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
  211. package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
  212. package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
  213. package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
  214. package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
  215. package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
  216. package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
  217. package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
  218. package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
  219. package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
  220. package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
  221. package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
  222. package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
  223. package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
  224. package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
  225. package/src/vendor/mermaid-ascii/class/types.ts +121 -0
  226. package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
  227. package/src/vendor/mermaid-ascii/er/types.ts +91 -0
  228. package/src/vendor/mermaid-ascii/index.ts +14 -0
  229. package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
  230. package/src/vendor/mermaid-ascii/parser.ts +645 -0
  231. package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
  232. package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
  233. package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
  234. package/src/vendor/mermaid-ascii/types.ts +164 -0
  235. package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
  236. package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
  237. package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
  238. package/src/version.ts +99 -0
  239. package/src/vterm/buffer.ts +218 -0
  240. package/src/vterm/terminal.ts +773 -0
  241. package/src/vterm.ts +8 -0
  242. package/src/which.ts +234 -0
  243. package/src/worker-host.ts +100 -0
  244. package/src/xml.ts +313 -0
@@ -0,0 +1,121 @@
1
+ function randu32() {
2
+ return crypto.getRandomValues(new Uint32Array(1))[0];
3
+ }
4
+
5
+ const EPOCH = 1420070400000;
6
+ const MAX_SEQ = 0x3fffff;
7
+
8
+ // Snowflake as a hex string (16 chars, zero-padded).
9
+ //
10
+ // Since this is not distributed (no machine ID needed), we use an extended
11
+ // 22-bit sequence instead of the standard 10-bit machine ID + 12-bit sequence.
12
+ //
13
+ type Snowflake = string & { readonly __brand: unique symbol };
14
+
15
+ namespace Snowflake {
16
+ // Hex string validation pattern (16 lowercase hex chars).
17
+ //
18
+ export const PATTERN = /^[0-9a-f]{16}$/;
19
+
20
+ // Epoch timestamp.
21
+ //
22
+ export const EPOCH_TIMESTAMP = EPOCH;
23
+
24
+ // Maximum sequence number.
25
+ //
26
+ export const MAX_SEQUENCE = MAX_SEQ;
27
+
28
+ // Formats a sequence and timestamp into a snowflake hex string.
29
+ //
30
+ // dt fits well within BigInt range: (dt << 22) | seq stays under 2^64 for
31
+ // any dt < 2^42 (~year 2154), so a single 64-bit format is exact — and
32
+ // measures ~1.7x faster than stitching four 16-bit hex segments.
33
+ //
34
+ export function formatParts(dt: number, seq: number): Snowflake {
35
+ return ((BigInt(dt) << 22n) | BigInt(seq)).toString(16).padStart(16, "0") as Snowflake;
36
+ }
37
+
38
+ // Snowflake generator type.
39
+ //
40
+ export class Source {
41
+ #seq = 0;
42
+ constructor(sequence: number = randu32() & MAX_SEQ) {
43
+ this.#seq = sequence & MAX_SEQ;
44
+ }
45
+
46
+ // Sequence number.
47
+ //
48
+ get sequence() {
49
+ return this.#seq & MAX_SEQ;
50
+ }
51
+ set sequence(v: number) {
52
+ this.#seq = v & MAX_SEQ;
53
+ }
54
+ reset() {
55
+ this.#seq = 0;
56
+ }
57
+
58
+ // Generates the next value as a hex string.
59
+ //
60
+ generate(timestamp: number): Snowflake {
61
+ const seq = (this.#seq + 1) & MAX_SEQ;
62
+ const dt = timestamp - EPOCH;
63
+ this.#seq = seq;
64
+ return formatParts(dt, seq);
65
+ }
66
+ }
67
+
68
+ // Gets the next snowflake given the timestamp.
69
+ //
70
+ let defaultSource: Source | undefined;
71
+ export function next(timestamp = Date.now()): Snowflake {
72
+ defaultSource ??= new Source();
73
+ return defaultSource.generate(timestamp);
74
+ }
75
+
76
+ // Validates a snowflake hex string.
77
+ //
78
+ export function valid(value: string): value is Snowflake {
79
+ return value.length === 16 && PATTERN.test(value);
80
+ }
81
+
82
+ // Returns the upper/lower boundaries for the given timestamp.
83
+ //
84
+ export function lowerbound(timelike: Date | number | Snowflake): Snowflake {
85
+ switch (typeof timelike) {
86
+ case "object": // Date
87
+ return formatParts(timelike.getTime() - EPOCH, 0);
88
+ case "number":
89
+ return formatParts(timelike - EPOCH, 0);
90
+ case "string": // Snowflake hex string
91
+ return timelike;
92
+ }
93
+ }
94
+ export function upperbound(timelike: Date | number | Snowflake): Snowflake {
95
+ switch (typeof timelike) {
96
+ case "object": // Date
97
+ return formatParts(timelike.getTime() - EPOCH, MAX_SEQ);
98
+ case "number":
99
+ return formatParts(timelike - EPOCH, MAX_SEQ);
100
+ case "string": // Snowflake hex string
101
+ return timelike;
102
+ }
103
+ }
104
+
105
+ // Returns the individual bits given the snowflake.
106
+ //
107
+ export function getSequence(value: Snowflake) {
108
+ return Number.parseInt(value.substring(8, 16), 16) & MAX_SEQ;
109
+ }
110
+ export function getTimestamp(value: Snowflake) {
111
+ const hi = Number.parseInt(value.substring(0, 8), 16);
112
+ const lo = Number.parseInt(value.substring(8, 16), 16);
113
+ // (hi:lo) >> 22 == hi * 2^10 + (lo >>> 22); at most ~2^42, exact in a double.
114
+ return hi * 1024 + (lo >>> 22) + EPOCH;
115
+ }
116
+ export function getDate(value: Snowflake) {
117
+ return new Date(getTimestamp(value));
118
+ }
119
+ }
120
+
121
+ export { Snowflake };
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Terminal stderr guard: keeps unmanaged fd-2 writes off the terminal while a
3
+ * TUI owns the viewport.
4
+ *
5
+ * On macOS, runtime diagnostics are written by the platform directly to file
6
+ * descriptor 2 at arbitrary times — e.g. libmalloc's "MallocStackLogging:
7
+ * can't turn off malloc stack logging because it was not enabled" when the OS
8
+ * broadcasts a memory-diagnostic event to long-lived processes. Those bytes
9
+ * bypass the renderer and paint straight into the viewport. Stripping the
10
+ * MallocStackLogging* env vars (cli.ts) only protects child processes; it
11
+ * cannot stop libmalloc inside THIS process from logging.
12
+ *
13
+ * Fix (mirrors openai/codex#24459): while the TUI owns the terminal, dup fd 2
14
+ * aside and dup2 a redirect target over it; restore the saved fd whenever
15
+ * terminal ownership is released (external editor, Ctrl+Z suspend, shutdown,
16
+ * crash restore). Unlike codex we redirect to the omp log file — not
17
+ * /dev/null — so the diagnostics stay greppable and Bun native-crash reports
18
+ * (which abort before any JS cleanup can restore fd 2) are preserved.
19
+ *
20
+ * Only dup/dup2 go through bun:ffi. fcntl is deliberately avoided: it is
21
+ * variadic, and the arm64-darwin ABI passes variadic arguments on the stack,
22
+ * so a fixed-arity FFI signature would read garbage for the third argument.
23
+ */
24
+ import { dlopen, FFIType } from "bun:ffi";
25
+ import * as fs from "node:fs";
26
+ import * as path from "node:path";
27
+ import { getLogPath } from "./dirs";
28
+
29
+ const STDOUT_FILENO = 1;
30
+ const STDERR_FILENO = 2;
31
+
32
+ interface LibcFdOps {
33
+ dup(fd: number): number;
34
+ dup2(oldFd: number, newFd: number): number;
35
+ }
36
+
37
+ let libcFdOpsCache: LibcFdOps | null | undefined;
38
+
39
+ function libcFdOps(): LibcFdOps | null {
40
+ if (libcFdOpsCache !== undefined) return libcFdOpsCache;
41
+ libcFdOpsCache = null;
42
+ if (process.platform === "win32") return null;
43
+ // Darwin: dyld resolves libSystem from the shared cache. Linux: glibc
44
+ // first, then the generic soname for musl-style layouts.
45
+ const candidates =
46
+ process.platform === "darwin" ? ["libSystem.B.dylib", "/usr/lib/libSystem.B.dylib"] : ["libc.so.6", "libc.so"];
47
+ for (const candidate of candidates) {
48
+ try {
49
+ const libc = dlopen(candidate, {
50
+ dup: { args: [FFIType.i32], returns: FFIType.i32 },
51
+ dup2: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 },
52
+ });
53
+ libcFdOpsCache = libc.symbols;
54
+ return libcFdOpsCache;
55
+ } catch {
56
+ // Try the next candidate; the guard stays inert if none load.
57
+ }
58
+ }
59
+ return libcFdOpsCache;
60
+ }
61
+
62
+ /**
63
+ * True when fd 2 writes would land on the same terminal the TUI paints to:
64
+ * both stdout and stderr are ttys backed by the same device file. A stderr
65
+ * the user already redirected (`2>file`, `2>/dev/null`, a different tty) must
66
+ * keep flowing untouched.
67
+ */
68
+ function stderrSharesStdoutTerminal(): boolean {
69
+ if (!process.stdout.isTTY || !process.stderr.isTTY) return false;
70
+ try {
71
+ const stdoutStat = fs.fstatSync(STDOUT_FILENO);
72
+ const stderrStat = fs.fstatSync(STDERR_FILENO);
73
+ return stdoutStat.dev === stderrStat.dev && stdoutStat.ino === stderrStat.ino;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /** Saved dup of the real stderr while suppression is active, else null. */
80
+ let savedStderrFd: number | null = null;
81
+
82
+ export interface SuppressTerminalStderrOptions {
83
+ /** Redirect target path; defaults to today's omp log file, then /dev/null. */
84
+ redirectPath?: string;
85
+ /** Bypass the macOS + same-terminal gate. Tests only. */
86
+ force?: boolean;
87
+ }
88
+
89
+ /**
90
+ * Redirect fd 2 away from the terminal while the TUI owns the viewport.
91
+ * Returns true when suppression is (already) active. No-op — returning
92
+ * false — off macOS, when stderr does not target the stdout terminal, or
93
+ * when the libc fd ops are unavailable.
94
+ */
95
+ export function suppressTerminalStderr(options?: SuppressTerminalStderrOptions): boolean {
96
+ if (savedStderrFd !== null) return true;
97
+ if (!options?.force && (process.platform !== "darwin" || !stderrSharesStdoutTerminal())) {
98
+ return false;
99
+ }
100
+ const libc = libcFdOps();
101
+ if (!libc) return false;
102
+
103
+ let redirectFd: number;
104
+ try {
105
+ const redirectPath = options?.redirectPath ?? getLogPath();
106
+ // getLogsDir() only computes the path; the logger creates it lazily, so
107
+ // on a fresh profile ~/.omp/logs may not exist yet. Create it here so
108
+ // diagnostics land in the log instead of falling through to /dev/null.
109
+ fs.mkdirSync(path.dirname(redirectPath), { recursive: true });
110
+ redirectFd = fs.openSync(redirectPath, "a");
111
+ } catch {
112
+ try {
113
+ redirectFd = fs.openSync("/dev/null", "w");
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+
119
+ const saved = libc.dup(STDERR_FILENO);
120
+ if (saved === -1) {
121
+ fs.closeSync(redirectFd);
122
+ return false;
123
+ }
124
+ if (libc.dup2(redirectFd, STDERR_FILENO) === -1) {
125
+ fs.closeSync(redirectFd);
126
+ fs.closeSync(saved);
127
+ return false;
128
+ }
129
+ fs.closeSync(redirectFd);
130
+ savedStderrFd = saved;
131
+ return true;
132
+ }
133
+
134
+ /**
135
+ * Re-point fd 2 at the saved terminal stderr. Safe to call unconditionally:
136
+ * no-op when suppression is not active. Called at every terminal-ownership
137
+ * release and by the postmortem fatal handlers before they print, so crash
138
+ * reports reach the real terminal.
139
+ */
140
+ export function restoreTerminalStderr(): void {
141
+ if (savedStderrFd === null) return;
142
+ const saved = savedStderrFd;
143
+ savedStderrFd = null;
144
+ libcFdOps()?.dup2(saved, STDERR_FILENO);
145
+ try {
146
+ fs.closeSync(saved);
147
+ } catch {
148
+ // The dup'ed fd is process-owned; a close failure leaves nothing to recover.
149
+ }
150
+ }
151
+
152
+ /** Whether fd 2 is currently redirected away from the terminal. */
153
+ export function isTerminalStderrSuppressed(): boolean {
154
+ return savedStderrFd !== null;
155
+ }