@oh-my-pi-zen/pi-utils 16.3.6-zen.1

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 (164) hide show
  1. package/CHANGELOG.md +245 -0
  2. package/README.md +36 -0
  3. package/dist/types/abortable.d.ts +32 -0
  4. package/dist/types/async.d.ts +6 -0
  5. package/dist/types/binary.d.ts +17 -0
  6. package/dist/types/cli.d.ts +117 -0
  7. package/dist/types/color.d.ts +102 -0
  8. package/dist/types/dirs.d.ts +260 -0
  9. package/dist/types/env.d.ts +74 -0
  10. package/dist/types/fetch-retry.d.ts +94 -0
  11. package/dist/types/format.d.ts +37 -0
  12. package/dist/types/frontmatter.d.ts +25 -0
  13. package/dist/types/fs-error.d.ts +31 -0
  14. package/dist/types/glob.d.ts +28 -0
  15. package/dist/types/index.d.ts +35 -0
  16. package/dist/types/json-parse.d.ts +48 -0
  17. package/dist/types/json.d.ts +4 -0
  18. package/dist/types/logger.d.ts +83 -0
  19. package/dist/types/loop-phase.d.ts +10 -0
  20. package/dist/types/mermaid-ascii.d.ts +11 -0
  21. package/dist/types/mime.d.ts +29 -0
  22. package/dist/types/module-timer.d.ts +1 -0
  23. package/dist/types/path-tree.d.ts +76 -0
  24. package/dist/types/path.d.ts +2 -0
  25. package/dist/types/peek-file.d.ts +29 -0
  26. package/dist/types/postmortem.d.ts +37 -0
  27. package/dist/types/procmgr.d.ts +29 -0
  28. package/dist/types/prompt.d.ts +18 -0
  29. package/dist/types/ptree.d.ts +108 -0
  30. package/dist/types/ring.d.ts +93 -0
  31. package/dist/types/runtime-install.d.ts +68 -0
  32. package/dist/types/sanitize-text.d.ts +29 -0
  33. package/dist/types/snowflake.d.ts +25 -0
  34. package/dist/types/stream.d.ts +68 -0
  35. package/dist/types/tab-spacing.d.ts +24 -0
  36. package/dist/types/temp.d.ts +17 -0
  37. package/dist/types/timing-buffer.d.ts +22 -0
  38. package/dist/types/tls-fetch.d.ts +37 -0
  39. package/dist/types/type-guards.d.ts +3 -0
  40. package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
  41. package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
  42. package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
  43. package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
  44. package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
  45. package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
  46. package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
  47. package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
  48. package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
  49. package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
  50. package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
  51. package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
  52. package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
  53. package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
  54. package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
  55. package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
  56. package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
  57. package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
  58. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
  59. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
  60. package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
  61. package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
  62. package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
  63. package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
  64. package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
  65. package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
  66. package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
  67. package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
  68. package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
  69. package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
  70. package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
  71. package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
  72. package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
  73. package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
  74. package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
  75. package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
  76. package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
  77. package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
  78. package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
  79. package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
  80. package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
  81. package/dist/types/which.d.ts +37 -0
  82. package/dist/types/worker-host.d.ts +47 -0
  83. package/package.json +62 -0
  84. package/src/abortable.ts +109 -0
  85. package/src/async.ts +50 -0
  86. package/src/binary.ts +50 -0
  87. package/src/cli.ts +457 -0
  88. package/src/color.ts +302 -0
  89. package/src/dirs.ts +925 -0
  90. package/src/env.ts +226 -0
  91. package/src/fetch-retry.ts +355 -0
  92. package/src/format.ts +113 -0
  93. package/src/frontmatter.ts +162 -0
  94. package/src/fs-error.ts +56 -0
  95. package/src/glob.ts +189 -0
  96. package/src/index.ts +55 -0
  97. package/src/json-parse.ts +612 -0
  98. package/src/json.ts +10 -0
  99. package/src/logger.ts +672 -0
  100. package/src/loop-phase.ts +49 -0
  101. package/src/mermaid-ascii.ts +31 -0
  102. package/src/mime.ts +159 -0
  103. package/src/module-timer.ts +148 -0
  104. package/src/path-tree.ts +147 -0
  105. package/src/path.ts +28 -0
  106. package/src/peek-file.ts +188 -0
  107. package/src/postmortem.ts +222 -0
  108. package/src/procmgr.ts +195 -0
  109. package/src/prompt.ts +562 -0
  110. package/src/ptree.ts +395 -0
  111. package/src/ring.ts +169 -0
  112. package/src/runtime-install.ts +372 -0
  113. package/src/sanitize-text.ts +96 -0
  114. package/src/snowflake.ts +121 -0
  115. package/src/stream.ts +432 -0
  116. package/src/tab-spacing.ts +336 -0
  117. package/src/temp.ts +134 -0
  118. package/src/timing-buffer.ts +47 -0
  119. package/src/tls-fetch.ts +178 -0
  120. package/src/type-guards.ts +11 -0
  121. package/src/vendor/mermaid-ascii/NOTICE +33 -0
  122. package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
  123. package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
  124. package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
  125. package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
  126. package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
  127. package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
  128. package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
  129. package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
  130. package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
  131. package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
  132. package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
  133. package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +215 -0
  134. package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
  135. package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
  136. package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
  137. package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
  138. package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
  139. package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
  140. package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
  141. package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
  142. package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
  143. package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
  144. package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
  145. package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
  146. package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
  147. package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
  148. package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
  149. package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
  150. package/src/vendor/mermaid-ascii/class/types.ts +121 -0
  151. package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
  152. package/src/vendor/mermaid-ascii/er/types.ts +91 -0
  153. package/src/vendor/mermaid-ascii/index.ts +14 -0
  154. package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
  155. package/src/vendor/mermaid-ascii/parser.ts +645 -0
  156. package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
  157. package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
  158. package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
  159. package/src/vendor/mermaid-ascii/types.ts +164 -0
  160. package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
  161. package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
  162. package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
  163. package/src/which.ts +232 -0
  164. package/src/worker-host.ts +92 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,245 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [16.3.1] - 2026-07-02
6
+
7
+ ### Fixed
8
+
9
+ - Fixed `parseJsonWithRepair` failing tool calls whose streamed arguments contain an unquoted string value (e.g. `{"paths": packages/foo/*, "i": "…"}`). Final parsing now recovers such barewords in object/array value position as strings, terminating at `,` / `}` / `]` / newline. Recovery deliberately refuses anything that could mask real structure or bad data — truncated values, tokens containing `"` / `{` / `[` or a key-like `:` (URL `://` and Windows `:\` colons stay literal), and non-finite atoms (`NaN`, `Infinity`, `undefined`) — and streaming partial parses still roll back unfinished barewords instead of committing them.
10
+
11
+ ## [16.3.0] - 2026-07-02
12
+
13
+ ### Added
14
+
15
+ - Added `wrapFetchForExtraCa` and `withExtraCaFetch` utility functions to apply `NODE_EXTRA_CA_CERTS` to Bun's `RequestInit.tls.ca` configuration.
16
+
17
+ ## [16.2.9] - 2026-06-30
18
+
19
+ ### Added
20
+
21
+ - Improved resilience in `fetchWithRetry()` by adding a response-body retry gate to handle deterministic provider failures that return retryable HTTP statuses.
22
+
23
+ ### Fixed
24
+
25
+ - Fixed YAML frontmatter parsing for skill descriptions containing unquoted colons (`: `), ensuring typed fields are correctly preserved without triggering unnecessary warnings.
26
+
27
+ ## [16.2.7] - 2026-06-30
28
+
29
+ ### Added
30
+
31
+ - Added a utility to detect binary files based on content sniffing.
32
+
33
+ ## [16.2.6] - 2026-06-29
34
+
35
+ ### Added
36
+
37
+ - Added `stripWindowsExtendedLengthPathPrefix()` utility to normalize `\\?\` and native Win32 path prefixes before Bun import or spawn calls.
38
+
39
+ ## [16.2.3] - 2026-06-28
40
+
41
+ ### Added
42
+
43
+ - Added `escapeXmlAttribute` utility function for safe XML attribute value encoding.
44
+
45
+ ### Fixed
46
+
47
+ - Fixed a crash in `ptree.ChildProcess.bytes()` and the `ssh://` read path when handling large subprocess outputs (over 128 KB) under Bun by ensuring it consistently returns a `Uint8Array`.
48
+
49
+ ## [16.2.0] - 2026-06-27
50
+
51
+ ### Added
52
+
53
+ - Added a relaxed JSON parser supporting single-quoted strings, unquoted keys, and comments.
54
+ - Added `parseStreamingJson` and `parseStreamingJsonThrottled` for robust, efficient parsing of truncated or incremental streaming JSON.
55
+ - Added an XDG-aware document conversion cache directory helper.
56
+ - Exported `removeWithRetries()` as a standalone asynchronous function to handle retry-on-EBUSY cleanup logic.
57
+
58
+ ### Changed
59
+
60
+ - Improved `readSseJson` to gracefully recover truncated or malformed final events using the streaming JSON parser, ending the stream cleanly instead of throwing.
61
+ - Increased the retry delay for EBUSY file-lock errors from 25ms to 50ms (extending the total retry window to 2 seconds) to improve reliability on Windows.
62
+
63
+ ## [16.1.8] - 2026-06-20
64
+
65
+ ### Added
66
+
67
+ - Exported `removeSyncWithRetries()` as a standalone function so tests that manage their own temp dirs can use the same retry-on-EBUSY cleanup logic as `TempDir.removeSync()`.
68
+
69
+ ## [16.1.3] - 2026-06-19
70
+
71
+ ### Changed
72
+
73
+ - Expanded the `TempDir` Windows retry window from 4×10ms to 40×25ms (1s total) to accommodate SQLite WAL/SHM file handle release delays
74
+
75
+ ### Fixed
76
+
77
+ - Made EPIPE rejections from IPC `send()` to worker subprocesses (`syscall: "send"`) non-fatal: the global `unhandledRejection` handler now logs and continues instead of terminating the session when an optional subsystem's pipe breaks. A broken optional subsystem (TTS/STT/tiny-title/MCP) can no longer crash the whole agent session mid-task. ([#2997](https://github.com/cagedbird043/oh-my-pi-zen/issues/2997))
78
+
79
+ ## [16.1.2] - 2026-06-19
80
+
81
+ ### Added
82
+
83
+ - Added `directoryExists(dir)` to `dirs`: resolves whether a path is an existing directory, returning `false` on any stat failure (ENOENT, permission, non-directory). Lets callers check a directory is safe to `chdir` into before `setProjectDir` throws.
84
+
85
+ ### Removed
86
+
87
+ - Removed the public `createAbortableStream` API from `@oh-my-pi-zen/pi-utils`. Consumers should use the lighter, direct-reader `abortableSource` async generator inside `@oh-my-pi-zen/pi-utils/stream` to avoid the extra ReadableStream wrapper layer and per-chunk enqueue overhead.
88
+
89
+ ## [16.0.11] - 2026-06-19
90
+
91
+ ### Removed
92
+
93
+ - Removed `getIndentation`, `setDefaultTabWidth`, and `getDefaultTabWidth` helpers
94
+
95
+ ## [16.0.8] - 2026-06-18
96
+
97
+ ### Changed
98
+
99
+ - Mermaid diagrams are now rendered to ASCII by a first-party vendored renderer (`src/vendor/mermaid-ascii`, derived from the MIT-licensed `beautiful-mermaid`, ASCII pipeline only) with terminal display width measured via `Bun.stringWidth` (grapheme-aware, correct for wide/East-Asian glyphs and emoji). Inline label formatting (HTML formatting tags and markdown emphasis) is now reduced to plain text instead of printed raw.
100
+
101
+ ### Removed
102
+
103
+ - Removed the external `beautiful-mermaid` dependency (and its transitive `elkjs`, ~3.13MB) in favor of the vendored ASCII renderer.
104
+
105
+ ## [16.0.3] - 2026-06-16
106
+
107
+ ### Added
108
+
109
+ - Added `escapeXmlText` utility to escape XML-significant characters `&`, `<`, and `>` in element body text
110
+ - Added `isTerminalHeadless()` / `setTerminalHeadless()` to centrally suppress real-terminal side effects (stdout escape/frame writes, stdin raw mode, CSI/OSC capability probes, SIGWINCH, window-title changes, emergency restore) under the test runtime. Defaults on when `bun test` sets `NODE_ENV=test`; terminal-contract tests opt out via `setTerminalHeadless(false)`
111
+
112
+ ## [15.13.3] - 2026-06-15
113
+
114
+ ### Added
115
+
116
+ - Added `installWorkerInbox(port)` / `consumeWorkerInbox()` to `@oh-my-pi-zen/pi-utils/worker-host`. A self-dispatching CLI host that imports a Bun worker module dynamically attaches the worker's real `message` listener after Bun flushes the messages the parent posted before spawn, dropping a synchronously-posted `init`. The host installs this buffering inbox synchronously in the entry's sync prefix so a listener exists at flush time; the worker module consumes it and binds the real handler, replaying anything buffered.
117
+
118
+ ## [15.13.1] - 2026-06-15
119
+
120
+ ### Added
121
+
122
+ - Added profile-aware directory helpers and isolated profile state roots, while keeping the install ID shared across profiles.
123
+ - Added a named-profile API to the `dirs` module — `setProfile()`, `getActiveProfile()`, `getProfileRootDir()`, and `normalizeProfileName()` — plus `resolveProfileEnv()`, which selects the active profile from `OMP_PROFILE` (canonical; takes precedence) then `PI_PROFILE` (legacy fallback, consulted only when `OMP_PROFILE` is unset).
124
+ - Added support for a runtime `overrides` map in `RuntimeInstallSpec`, which is now written into generated runtime `package.json` manifests to force dependency pins (including transitive ones) across the runtime tree
125
+ - Added a lightweight loop-phase breadcrumb stack (`pushLoopPhase`/`popLoopPhase`/`currentLoopPhase`, plus `takeRecentLoopPhase` which returns the live phase or the most recently popped one and clears it) so the TUI event-loop watchdog can attribute a main-thread block to the phase that caused it — including a synchronous phase already popped before the watchdog's delayed tick runs ([#2485](https://github.com/cagedbird043/oh-my-pi-zen/issues/2485))
126
+ - Added `FetchWithRetryOptions.timeout` (forwarded to the underlying `fetch` call). `false` disables Bun's native ~300s pre-response timeout; a positive number overrides the ceiling. Bare browser/Node fetch ignores it ([#2422](https://github.com/cagedbird043/oh-my-pi-zen/issues/2422))
127
+ - Added the side-effect-free `@oh-my-pi-zen/pi-utils/worker-host` module (`declareWorkerHostEntry()` / `workerHostEntry()`), extracted from `env` (still re-exported there) so worker spawn sites can resolve the self-dispatching CLI host entry without importing `env`'s side-effecting module graph.
128
+
129
+ ### Fixed
130
+
131
+ - Fixed profile directory isolation when a profile's agent `.env` customizes directory roots: directory-affecting keys (`XDG_DATA_HOME`/`XDG_STATE_HOME`/`XDG_CACHE_HOME`, and a default-mode `PI_CODING_AGENT_DIR`) are now honored. The `env` loader rebuilds the `dirs` resolver after applying `.env` files (`refreshDirsFromEnv()`), so a profile `.env` that points XDG roots elsewhere no longer leaks state into the home-based config dir.
132
+ - Made `TempDir` cleanup retry transient Windows `EBUSY`/`EPERM`/`ENOTEMPTY` removal failures so tests are less likely to fail when deleting just-used temp directories.
133
+ - Fixed `installRuntimeModuleResolver()` to keep bare requests from runtime-cache modules inside that registered runtime before falling back to host/workspace packages.
134
+
135
+ ## [15.12.4] - 2026-06-13
136
+
137
+ ### Fixed
138
+
139
+ - Fixed abortable stream wrappers to cancel the source stream on abort, so timeout watchdogs release upstream HTTP bodies instead of only stopping the local reader.
140
+
141
+ ## [15.12.0] - 2026-06-12
142
+
143
+ ### Added
144
+
145
+ - Added `runtime-install`: shared on-demand runtime dependency support — `ensureRuntimeInstalled()` (locked, idempotent `bun install` of a pinned dependency set into a cache dir) and a multi-root `installRuntimeModuleResolver()`/`resolveRuntimeModule()` for loading those graphs inside compiled binaries (Bun #1763). Extracted from the coding-agent tiny-model worker; now also backs Mnemopi's on-demand fastembed runtime ([#2389](https://github.com/cagedbird043/oh-my-pi-zen/issues/2389))
146
+ - Added `getFastembedRuntimeDir()` (~/.omp/cache/fastembed-runtime) alongside `getFastembedCacheDir()`
147
+
148
+ ## [15.11.4] - 2026-06-12
149
+
150
+ ### Added
151
+
152
+ - Added `getEditorConfigFormatting(file)`: returns the `.editorconfig`-pinned `tabSize`/`insertSpaces` (both optional, no fallback) so LSP-format callers can layer per-file defaults under it without paving over silence with the renderer's display tab width ([#2329](https://github.com/cagedbird043/oh-my-pi-zen/issues/2329)).
153
+
154
+ ## [15.11.3] - 2026-06-11
155
+
156
+ ### Added
157
+
158
+ - Added `getEditorConfigFormatting(file)`: returns the `.editorconfig`-pinned `tabSize`/`insertSpaces` (both optional, no fallback) so LSP-format callers can layer per-file defaults under it without paving over silence with the renderer's display tab width ([#2329](https://github.com/cagedbird043/oh-my-pi-zen/issues/2329)).
159
+
160
+ ## [15.11.1] - 2026-06-11
161
+
162
+ ### Fixed
163
+
164
+ - Fixed cleanup reentry noise during fatal shutdown: recursive cleanup requests now no-op idempotently instead of logging repeated `Cleanup invoked recursively` errors ([#2284](https://github.com/cagedbird043/oh-my-pi-zen/issues/2284)).
165
+
166
+ ## [15.11.0] - 2026-06-10
167
+
168
+ ### Added
169
+
170
+ - Added the `path-tree` module (`buildPathTree`, `walkPathTree`, `formatGroupedPaths`, `isUrlLikePath`), moved from the coding agent's grouped file output so compaction file lists can share the same prefix-folded directory-tree rendering; `formatGroupedPaths` gains an optional `annotate` callback for per-file suffixes
171
+
172
+ ### Fixed
173
+
174
+ - Fixed the `{{join}}` prompt helper joining with a literal two-character `\n` when templates pass `"\n"` as the separator — Handlebars string literals carry no escape processing. The separator now unescapes `\n`/`\t`, matching the `{{#list}}` helper's documented convention (visible as literal `\n` between paths in compaction `<read-files>` lists).
175
+
176
+ ## [15.10.11] - 2026-06-10
177
+
178
+ ### Added
179
+
180
+ - Restored `PI_DEBUG_STARTUP` streaming startup markers: `logger.time` now writes a synchronous `[startup] <op>:start` / `:done` / `:fail` stderr line per phase (independent of `PI_TIMING`), so a startup that hangs hard still names the phase it is stuck in — the `PI_TIMING` tree only prints after startup completes and is structurally unable to diagnose a hang. The CLI runner emits `cli:load:<name>` markers around each lazily-imported command module for the same reason.
181
+ - Added `logger.openSpanPath()`: ops of the currently-open timing-span chain (root → deepest), used by the coding agent's startup watchdog to name the in-flight phase of a stalled startup.
182
+ - Added `declareWorkerHostEntry()` / `workerHostEntry()` (env): self-dispatching CLI entrypoints declare `Bun.main` as the worker host so worker spawn sites can re-enter the single entry module with `WorkerOptions.argv` selectors across source, npm-bundle, and compiled distributions
183
+
184
+ ### Changed
185
+
186
+ - Changed `prompt.compile()` to cache compiled templates by the raw template string so repeated calls reuse the same compiled function without re-disambiguating
187
+ - `Snowflake.formatParts` packs the id as a single 64-bit BigInt hex format instead of stitching four 16-bit segments (simpler and ~1.7x faster), and `getTimestamp` extracts via exact double arithmetic instead of a BigInt round-trip. Output is bit-identical.
188
+ - Logger initialization is lazy: the winston logger, file transport, and log-directory creation now happen on first log emission instead of at module import (the import previously cost ~8ms of fs work on the CLI startup path); the in-memory timing infrastructure never touches winston
189
+ - `prompt.format()` post-processing got cheap per-line guards and a single-pass ASCII-symbol replacement (was 7 chained regex passes per line), roughly halving render post-processing cost; output is byte-identical
190
+
191
+ ### Fixed
192
+
193
+ - Fixed `prompt.format()` so ASCII symbol replacements such as `-->` and `!=` still run on lines containing a closing HTML comment token when not inside a comment
194
+ - `isCompiledBinary()` now also honors a define-folded `process.env.PI_COMPILED` (only `Bun.env` was checked), so builds that constant-fold `process.env` keep compiled-binary detection without relying on `import.meta.url` bunfs markers
195
+ - `omp <cmd> --help` now loads only the requested command module instead of the entire command table, so an unrelated command whose import graph hangs or crashes can no longer take down every per-command help invocation.
196
+
197
+ ## [15.10.8] - 2026-06-09
198
+
199
+ ### Removed
200
+
201
+ - Removed the exported `hookFetch` API, which previously intercepted `globalThis.fetch` via middleware handlers
202
+ - Removed `hookFetch` from the package entrypoint, so imports from `@.../utils` no longer provide this fetch interception helper
203
+
204
+ ## [15.10.0] - 2026-06-06
205
+
206
+ ### Changed
207
+
208
+ - `logger.printTimings()` (the `PI_TIMING` startup tree) now surfaces two previously-invisible regions: a `(before instrumentation)` line for runtime init / uncaptured pre-marker work, and an `(unattributed self)` line for the root span's own untimed work so the gap between visible top-level spans and `Total` is no longer swallowed. `Total` is now labelled `(since first marker)` to make the window explicit. The restored `module-timer.ts` preload can feed module spans into the report: each module records `onLoad` → final top-level marker as `total`, a prepended body marker → final marker as `body/TLA`, and resolved static imports as a bounded dependency tree so the report separates graph wait from actual top-level module work.
209
+
210
+ ## [15.9.2] - 2026-06-05
211
+
212
+ ### Added
213
+
214
+ - Added `getAuthBrokerSnapshotCachePath()` with `OMP_AUTH_BROKER_SNAPSHOT_CACHE` override support for isolating the encrypted broker snapshot cache.
215
+
216
+ ## [15.9.1] - 2026-06-04
217
+
218
+ ### Fixed
219
+
220
+ - Hardened `getIndentation` against malformed paths: any filesystem error from the `.editorconfig` probe (e.g. `ENAMETOOLONG` on oversized garbage path segments) is now swallowed and cached as a miss instead of escaping and crashing the TUI mid-render ([#1871](https://github.com/cagedbird043/oh-my-pi-zen/issues/1871)).
221
+ - Fixed `getIndentation` (and the edit renderer's `replaceTabs` callers) crashing with `ENAMETOOLONG`/`ENOTDIR`/etc. when handed a path with an overlong component or a non-directory in its parent chain. Editorconfig discovery now short-circuits to the default tab width on any path component above `NAME_MAX` (255 bytes) and absorbs any `FsError` while walking the editorconfig chain — best-effort discovery must never escape as an uncaught exception ([#1872](https://github.com/cagedbird043/oh-my-pi-zen/issues/1872)).
222
+
223
+ ## [15.9.0] - 2026-06-04
224
+
225
+ ### Added
226
+
227
+ - Added color helpers `colorLuma` (perceptual luma), `relativeLuminance` (WCAG, linearized sRGB), and `hslToHex` to the color utilities. The luminance helpers parse `#rgb`/`#rrggbb` hex and 256-color palette indices, returning `undefined` for unparseable values.
228
+ - Added `peekFileEnds`, a single-open head-and-tail file peek helper that reuses the head bytes for the tail when the file fits the head window.
229
+ - Added `peekFileTail`, the tail mirror of `peekFile`: reads up to the last `maxBytes` of a file ending at EOF, reusing the same pooled-buffer strategy (no per-call allocation for small reads).
230
+
231
+ ## [15.7.3] - 2026-05-31
232
+
233
+ ### Added
234
+
235
+ - Added `getFastembedCacheDir` to return the FastEmbed model cache directory under ~/.omp/cache/fastembed
236
+
237
+ ### Fixed
238
+
239
+ - Fixed `$flag` environment parsing to accept lowercase truthy values such as `y`, `true`, `yes`, and `on`
240
+
241
+ ## [15.6.0] - 2026-05-30
242
+
243
+ ### Added
244
+
245
+ - Added an XDG-aware tiny-title model cache directory helper for coding-agent local title models.
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @oh-my-pi-zen/pi-utils
2
+
3
+ Shared utilities for [oh-my-pi](https://github.com/cagedbird043/oh-my-pi-zen) packages. Zero ceremony, Bun-first.
4
+
5
+ ## Notable modules
6
+
7
+ | Module | Purpose |
8
+ | --- | --- |
9
+ | `logger` | Centralized logger writing to `~/.omp/logs/` with rotation (TUI-safe — never stdout) |
10
+ | `prompt` | Handlebars-based prompt templating and formatting helpers |
11
+ | `dirs` | Path helpers for omp config directories (`~/.omp`, XDG-aware on Linux) |
12
+ | `stream` | `readStream` / `readLines` helpers over `ReadableStream` |
13
+ | `ptree` / `procmgr` | Process trees, `ChildProcess` wrapper, process lifecycle management |
14
+ | `postmortem` | Cleanup callbacks on exit, signals, and fatal exceptions |
15
+ | `which` | `$which()` binary lookup with caching |
16
+ | `fetch-retry` | `fetch` with retry/backoff policies |
17
+ | `fs-error` | Errno guards (`isEnoent` and friends) |
18
+ | `env` / `worker-host` | Environment plumbing and side-effect-free worker-host entry contract (`workerHostEntry`) |
19
+ | `abortable` / `async` | AbortSignal-aware stream/promise helpers |
20
+ | `peek-file` | Read the first N bytes of a file with pooled buffers |
21
+ | `frontmatter`, `glob`, `mime`, `temp`, `format`, `color`, `snowflake`, `tab-spacing`, `path-tree`, `sanitize-text` | Smaller single-purpose helpers |
22
+
23
+ Import from the root barrel or per-module subpaths (`@oh-my-pi-zen/pi-utils/<module>`).
24
+
25
+ ## Install
26
+
27
+ ```sh
28
+ bun add @oh-my-pi-zen/pi-utils
29
+ ```
30
+
31
+ Ships TypeScript source directly (no build step); requires Bun ≥ 1.3.14.
32
+
33
+ ## References
34
+
35
+ - [Monorepo README](https://github.com/cagedbird043/oh-my-pi-zen#readme)
36
+ - [CHANGELOG](./CHANGELOG.md)
@@ -0,0 +1,32 @@
1
+ export declare class AbortError extends Error {
2
+ constructor(signal: AbortSignal);
3
+ }
4
+ /**
5
+ * Abortable async iteration over a {@link ReadableStream}. Reads the source
6
+ * reader directly and yields each chunk, so the consumer's `for await` drives a
7
+ * single read loop with no intermediate stream or per-chunk enqueue.
8
+ *
9
+ * Unlike `stream.pipeThrough(..., { signal })`, this explicitly cancels the
10
+ * source reader on abort or early `break`, propagating HTTP-client disconnects
11
+ * and watchdog timeouts to the backend request instead of only stopping the
12
+ * local consumer. On abort it throws {@link AbortError}; the lock is released
13
+ * on completion, abort, throw, or early exit. The source is cancelled only on
14
+ * abort or early exit — never on natural EOF.
15
+ */
16
+ export declare function abortableSource<T>(stream: ReadableStream<T>, signal?: AbortSignal): AsyncGenerator<T>;
17
+ /**
18
+ * Runs a promise-returning function (`pr`). If the given AbortSignal is aborted before or during
19
+ * execution, the promise is rejected with a standard error.
20
+ *
21
+ * @param signal - Optional AbortSignal to cancel the operation
22
+ * @param pr - Function returning a promise to run
23
+ * @returns Promise resolving as `pr` would, or rejecting on abort
24
+ */
25
+ export declare function untilAborted<T>(signal: AbortSignal | undefined | null, pr: Promise<T> | (() => Promise<T>)): Promise<T>;
26
+ /**
27
+ * Memoizes a function with no arguments, calling it once and caching the result.
28
+ *
29
+ * @param fn - Function to be called once
30
+ * @returns A function that returns the cached result of `fn`
31
+ */
32
+ export declare function once<T>(fn: () => T): () => T;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Wrap a promise with a timeout and optional abort signal.
3
+ * Rejects with the given message if the timeout fires first.
4
+ * Cleans up all listeners on settlement.
5
+ */
6
+ export declare function withTimeout<T>(promise: Promise<T>, ms: number, message: string, signal?: AbortSignal): Promise<T>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Classify an in-memory byte header as binary (non-UTF-8-text).
3
+ *
4
+ * Binary when the header contains a NUL byte (true binary, plus UTF-16/UTF-32
5
+ * text whose ASCII range is NUL-padded) or when it is not valid UTF-8. The
6
+ * decode runs in streaming mode so a multibyte sequence truncated at the header
7
+ * boundary is tolerated, while any genuinely invalid byte still fails — matching
8
+ * the strict `fatal` decode the `local://`/`ssh://` read paths already use.
9
+ */
10
+ export declare function isProbablyBinaryHeader(header: Uint8Array): boolean;
11
+ /**
12
+ * Sniff the first {@link BINARY_SNIFF_BYTES} of `filePath` and report whether it
13
+ * is binary (non-UTF-8-text). See {@link isProbablyBinaryHeader} for the rule.
14
+ */
15
+ export declare function isProbablyBinary(filePath: string, maxBytes?: number): Promise<boolean>;
16
+ /** Synchronous {@link isProbablyBinary}. */
17
+ export declare function isProbablyBinarySync(filePath: string, maxBytes?: number): boolean;
@@ -0,0 +1,117 @@
1
+ export interface FlagDescriptor<K extends "string" | "boolean" | "integer" = "string" | "boolean" | "integer"> {
2
+ kind: K;
3
+ description?: string;
4
+ char?: string;
5
+ default?: unknown;
6
+ multiple?: boolean;
7
+ options?: readonly string[];
8
+ required?: boolean;
9
+ }
10
+ export interface ArgDescriptor {
11
+ kind: "string";
12
+ description?: string;
13
+ required?: boolean;
14
+ multiple?: boolean;
15
+ options?: readonly string[];
16
+ }
17
+ interface FlagInput {
18
+ description?: string;
19
+ char?: string;
20
+ default?: unknown;
21
+ multiple?: boolean;
22
+ options?: readonly string[];
23
+ required?: boolean;
24
+ }
25
+ interface ArgInput {
26
+ description?: string;
27
+ required?: boolean;
28
+ multiple?: boolean;
29
+ options?: readonly string[];
30
+ }
31
+ /** Builders that match the `Flags.*()` / `Args.*()` API from oclif. */
32
+ export declare const Flags: {
33
+ string<T extends FlagInput>(opts?: T): FlagDescriptor<"string"> & T;
34
+ boolean<T extends FlagInput>(opts?: T): FlagDescriptor<"boolean"> & T;
35
+ integer<T extends FlagInput & {
36
+ default?: number;
37
+ }>(opts?: T): FlagDescriptor<"integer"> & T;
38
+ };
39
+ export declare const Args: {
40
+ string<T extends ArgInput>(opts?: T): ArgDescriptor & T;
41
+ };
42
+ type FlagValue<D extends FlagDescriptor> = D["kind"] extends "boolean" ? D extends {
43
+ default: boolean;
44
+ } ? boolean : boolean | undefined : D["kind"] extends "integer" ? D extends {
45
+ default: number;
46
+ } ? number : number | undefined : D extends {
47
+ multiple: true;
48
+ } ? string[] | undefined : string | undefined;
49
+ type ArgValue<D extends ArgDescriptor> = D extends {
50
+ multiple: true;
51
+ } ? string[] | undefined : string | undefined;
52
+ type FlagValues<T extends Record<string, FlagDescriptor>> = {
53
+ [K in keyof T]: FlagValue<T[K]>;
54
+ };
55
+ type ArgValues<T extends Record<string, ArgDescriptor>> = {
56
+ [K in keyof T]: ArgValue<T[K]>;
57
+ };
58
+ export interface ParseOutput<F extends Record<string, FlagDescriptor> = Record<string, FlagDescriptor>, A extends Record<string, ArgDescriptor> = Record<string, ArgDescriptor>> {
59
+ flags: FlagValues<F>;
60
+ args: ArgValues<A>;
61
+ argv: string[];
62
+ }
63
+ export interface CommandCtor {
64
+ new (argv: string[], config: CliConfig): Command;
65
+ description?: string;
66
+ hidden?: boolean;
67
+ strict?: boolean;
68
+ aliases?: string[];
69
+ examples?: string[];
70
+ flags?: Record<string, FlagDescriptor>;
71
+ args?: Record<string, ArgDescriptor>;
72
+ }
73
+ /** Configuration passed to every command instance and help renderers. */
74
+ export interface CliConfig {
75
+ bin: string;
76
+ version: string;
77
+ /** All registered commands keyed by their canonical name. */
78
+ commands: Map<string, CommandCtor>;
79
+ }
80
+ /** Minimal Command base matching the oclif surface we use. */
81
+ export declare abstract class Command {
82
+ argv: string[];
83
+ config: CliConfig;
84
+ constructor(argv: string[], config: CliConfig);
85
+ abstract run(): Promise<void>;
86
+ /**
87
+ * Parse argv against the static `flags` and `args` declared on the
88
+ * concrete command class. Returns a typed `{ flags, args, argv }` object.
89
+ */
90
+ parse<C extends CommandCtor>(_Cmd: C): Promise<ParseOutput<NonNullable<C["flags"]> extends Record<string, FlagDescriptor> ? NonNullable<C["flags"]> : Record<string, FlagDescriptor>, NonNullable<C["args"]> extends Record<string, ArgDescriptor> ? NonNullable<C["args"]> : Record<string, ArgDescriptor>>>;
91
+ }
92
+ /** Render full root help: header, default command details, subcommand list. */
93
+ export declare function renderRootHelp(config: CliConfig): void;
94
+ /** Render help for a single command. */
95
+ export declare function renderCommandHelp(bin: string, id: string, Cmd: CommandCtor): void;
96
+ /** A lazily-loaded command: canonical name, loader, and optional aliases. */
97
+ export interface CommandEntry {
98
+ name: string;
99
+ load: () => Promise<CommandCtor>;
100
+ aliases?: string[];
101
+ }
102
+ export interface RunOptions {
103
+ bin: string;
104
+ version: string;
105
+ argv: string[];
106
+ commands: CommandEntry[];
107
+ /** Custom help renderer. Receives fully-populated config. */
108
+ help?: (config: CliConfig) => Promise<void> | void;
109
+ }
110
+ /**
111
+ * Main entry point — replaces `run()` from @oclif/core.
112
+ *
113
+ * Each command is explicitly registered with a lazy loader.
114
+ * No filesystem scanning, no plugin system, no package.json reading.
115
+ */
116
+ export declare function run(opts: RunOptions): Promise<void>;
117
+ export {};
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Color manipulation utilities for hex colors.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { hexToHsv, hsvToHex } from "@oh-my-pi-zen/pi-utils";
7
+ *
8
+ * // Rotate the hue by 90°
9
+ * const hsv = hexToHsv("#4ade80");
10
+ * hsv.h = (hsv.h + 90) % 360;
11
+ * const newHex = hsvToHex(hsv);
12
+ * ```
13
+ */
14
+ export interface HSV {
15
+ /** Hue in degrees (0-360) */
16
+ h: number;
17
+ /** Saturation (0-1) */
18
+ s: number;
19
+ /** Value/brightness (0-1) */
20
+ v: number;
21
+ }
22
+ export interface RGB {
23
+ /** Red (0-255) */
24
+ r: number;
25
+ /** Green (0-255) */
26
+ g: number;
27
+ /** Blue (0-255) */
28
+ b: number;
29
+ }
30
+ /**
31
+ * Parse a hex color string to RGB.
32
+ * Supports #RGB, #RRGGBB formats.
33
+ */
34
+ export declare function hexToRgb(hex: string): RGB;
35
+ /**
36
+ * Convert RGB to hex color string.
37
+ */
38
+ export declare function rgbToHex(rgb: RGB): string;
39
+ /**
40
+ * Convert RGB to HSV.
41
+ */
42
+ export declare function rgbToHsv(rgb: RGB): HSV;
43
+ /**
44
+ * Convert HSV to RGB.
45
+ */
46
+ export declare function hsvToRgb(hsv: HSV): RGB;
47
+ /**
48
+ * Convert hex color to HSV.
49
+ */
50
+ export declare function hexToHsv(hex: string): HSV;
51
+ /**
52
+ * Convert HSV to hex color.
53
+ */
54
+ export declare function hsvToHex(hsv: HSV): string;
55
+ /**
56
+ * Shift the hue of a hex color by a given number of degrees.
57
+ */
58
+ export declare function shiftHue(hex: string, degrees: number): string;
59
+ export interface HSVAdjustment {
60
+ /** Hue shift in degrees (additive) */
61
+ h?: number;
62
+ /** Saturation multiplier */
63
+ s?: number;
64
+ /** Value/brightness multiplier */
65
+ v?: number;
66
+ }
67
+ /**
68
+ * Adjust HSV components of a hex color.
69
+ *
70
+ * @param hex - Hex color string (#RGB or #RRGGBB)
71
+ * @param adj - Adjustments: h is additive degrees, s and v are multipliers
72
+ * @returns New hex color string
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * // Shift hue +60°, reduce saturation to 71%
77
+ * adjustHsv("#00ff88", { h: 60, s: 0.71 }) // "#4a9eff"
78
+ * ```
79
+ */
80
+ export declare function adjustHsv(hex: string, adj: HSVAdjustment): string;
81
+ /**
82
+ * Convert HSL (h: 0-360, s: 0-1, l: 0-1) to a CSS hex string.
83
+ */
84
+ export declare function hslToHex(h: number, s: number, l: number): string;
85
+ /**
86
+ * Perceptual luma (gamma-encoded BT.709 weights over raw sRGB), normalized to 0..1.
87
+ *
88
+ * Accepts a hex string (`#rgb` / `#rrggbb`) or a 256-color palette index; returns
89
+ * `undefined` for var refs, empty strings, or anything unparseable.
90
+ *
91
+ * Cheap and good enough for a light/dark *classification* threshold. NOT suitable
92
+ * for contrast ratios — use {@link relativeLuminance} for those.
93
+ */
94
+ export declare function colorLuma(value: string | number): number | undefined;
95
+ /**
96
+ * WCAG 2.x relative luminance (BT.709 weights over linearized sRGB), normalized to
97
+ * 0..1. This is the value the WCAG contrast-ratio formula expects.
98
+ *
99
+ * Accepts a hex string (`#rgb` / `#rrggbb`) or a 256-color palette index; returns
100
+ * `undefined` for var refs, empty strings, or anything unparseable.
101
+ */
102
+ export declare function relativeLuminance(value: string | number): number | undefined;