@xiaoso/dsh-tool-plus 0.1.0-beta.3 → 0.1.0-beta.4

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 (155) hide show
  1. package/lib/abort-D1_Z5Ut5.mjs +51 -0
  2. package/lib/abort-dIkZBV7Q.mjs +51 -0
  3. package/lib/amazon-bedrock-B0nxT06F.mjs +917 -0
  4. package/lib/amazon-bedrock-WQhvjHYu.mjs +917 -0
  5. package/lib/anthropic-CJCauvYz.mjs +3115 -0
  6. package/lib/anthropic-CZghe_9U.mjs +3115 -0
  7. package/lib/anthropic-sCMH7JID.mjs +262 -0
  8. package/lib/anthropic-tTntD8BD.mjs +262 -0
  9. package/lib/api-key-login-BzqNdM2y.mjs +157 -0
  10. package/lib/api-key-login-CWVwvHT1.mjs +157 -0
  11. package/lib/aws-credentials-CXryMobh.mjs +867 -0
  12. package/lib/aws-credentials-ChG79ey-.mjs +867 -0
  13. package/lib/azure-openai-responses-Cgmc-rbL.mjs +274 -0
  14. package/lib/azure-openai-responses-DuukbD0F.mjs +274 -0
  15. package/lib/bun-shim.mjs +81 -6
  16. package/lib/bundled-references-Cavaffah.mjs +55 -0
  17. package/lib/bundled-references-sLN0YdXU.mjs +55 -0
  18. package/lib/callback-server-CQ5Jo8F-.mjs +398 -0
  19. package/lib/callback-server-DDSrZ6l_.mjs +398 -0
  20. package/lib/client.js +1 -1
  21. package/lib/cursor-B3zg7EHj.mjs +273 -0
  22. package/lib/cursor-B6TUEmgP.mjs +135 -0
  23. package/lib/cursor-D8gvtTL3.mjs +135 -0
  24. package/lib/cursor-DkSX2h3H.mjs +5441 -0
  25. package/lib/cursor-aJrXeYCb.mjs +273 -0
  26. package/lib/cursor-mJQwUKXK.mjs +5441 -0
  27. package/lib/device-code-DBo3B5nh.mjs +51 -0
  28. package/lib/device-code-DIb0FU-H.mjs +51 -0
  29. package/lib/devin-BHZZyMhO.mjs +95 -0
  30. package/lib/devin-BK7DHeca.mjs +114 -0
  31. package/lib/devin-CfMp3cXS.mjs +95 -0
  32. package/lib/devin-D9zR5B_R.mjs +542 -0
  33. package/lib/devin-Q1yem3lp.mjs +114 -0
  34. package/lib/devin-vNZTNUON.mjs +542 -0
  35. package/lib/error-Dd8DIqs-.mjs +1449 -0
  36. package/lib/error-yCSG0mGC.mjs +1449 -0
  37. package/lib/event-stream-BbFZJy-z.mjs +151 -0
  38. package/lib/event-stream-loUa0s1T.mjs +151 -0
  39. package/lib/fs-promises-shim-Bx0fpxuk.mjs +57 -0
  40. package/lib/github-copilot-B1yiyVRh.mjs +104 -0
  41. package/lib/github-copilot-BWT3eAOF.mjs +208 -0
  42. package/lib/github-copilot-CYkjeTi3.mjs +208 -0
  43. package/lib/github-copilot-jscUZ8Hf.mjs +104 -0
  44. package/lib/gitlab-duo-CM7Ref1H.mjs +155 -0
  45. package/lib/gitlab-duo-gubO4FNw.mjs +155 -0
  46. package/lib/gitlab-duo-workflow-BGAF6JDw.mjs +91 -0
  47. package/lib/gitlab-duo-workflow-D78tdn8E.mjs +91 -0
  48. package/lib/google-BS4T25Pn.mjs +30 -0
  49. package/lib/google-TFe-7-of.mjs +30 -0
  50. package/lib/google-antigravity-DGmwC2KV.mjs +198 -0
  51. package/lib/google-antigravity-DNYhgTpA.mjs +198 -0
  52. package/lib/google-auth-B6gjsZvg.mjs +232 -0
  53. package/lib/google-auth-De7_s3eo.mjs +232 -0
  54. package/lib/google-gemini-cli-CWu8HvXW.mjs +211 -0
  55. package/lib/google-gemini-cli-Cb97bzQc.mjs +850 -0
  56. package/lib/google-gemini-cli-Cqds_Q5T.mjs +850 -0
  57. package/lib/google-gemini-cli-Dsztq0N5.mjs +211 -0
  58. package/lib/google-oauth-shared-CuEHaDDj.mjs +147 -0
  59. package/lib/google-oauth-shared-yqlyqPPC.mjs +147 -0
  60. package/lib/google-shared-CgKmmdO3.mjs +760 -0
  61. package/lib/google-shared-Dr-VG-ap.mjs +760 -0
  62. package/lib/google-vertex-BDN2tLaD.mjs +99 -0
  63. package/lib/google-vertex-DopVOQOG.mjs +99 -0
  64. package/lib/identity-Da1GPU9a.mjs +219 -0
  65. package/lib/identity-xRP7_eSR.mjs +219 -0
  66. package/lib/idle-iterator-Baoyn3Hp.mjs +366 -0
  67. package/lib/idle-iterator-DUjKQEGT.mjs +366 -0
  68. package/lib/index.mjs +1731 -1178
  69. package/lib/kimi--wUOWER5.mjs +236 -0
  70. package/lib/kimi-Dh0pv3WZ.mjs +236 -0
  71. package/lib/markit-BL3cH47U.mjs +1417 -0
  72. package/lib/markit-BNiNY6SE.mjs +1417 -0
  73. package/lib/minimax-code-DCbuA6Yk.mjs +53 -0
  74. package/lib/minimax-code-Uq8Qu9LW.mjs +53 -0
  75. package/lib/models-D_XnT6ay.mjs +104660 -0
  76. package/lib/models-SGASygJ3.mjs +104660 -0
  77. package/lib/ollama-DPzBmq89.mjs +589 -0
  78. package/lib/ollama-DoBz5SFm.mjs +589 -0
  79. package/lib/openai-codex-Bi-4S4PX.mjs +255 -0
  80. package/lib/openai-codex-Ch8CkT3p.mjs +255 -0
  81. package/lib/openai-codex-responses-CvxL_FMF.mjs +3144 -0
  82. package/lib/openai-codex-responses-pejZgdZo.mjs +3144 -0
  83. package/lib/openai-completions-CF8IyNtP.mjs +1360 -0
  84. package/lib/openai-completions-DIf361kF.mjs +1360 -0
  85. package/lib/openai-reasoning-fallback-Nj6yB2rY.mjs +286 -0
  86. package/lib/openai-reasoning-fallback-OdJREP1o.mjs +286 -0
  87. package/lib/openai-responses-BqXJInHX.mjs +867 -0
  88. package/lib/openai-responses-Db_a-U4-.mjs +867 -0
  89. package/lib/opencode-DhMolSoB.mjs +38 -0
  90. package/lib/opencode-Rq_Q2vAO.mjs +38 -0
  91. package/lib/perplexity-C4iLVMUj.mjs +194 -0
  92. package/lib/perplexity-Uu7Jzg5b.mjs +194 -0
  93. package/lib/proxy-DDqrmjjC.mjs +190 -0
  94. package/lib/proxy-pM_NIv-H.mjs +190 -0
  95. package/lib/request-debug-HJMoJncL.mjs +183 -0
  96. package/lib/sanitize-text-CfcXzw5X.mjs +33 -0
  97. package/lib/scrapers-BkFfcPHj.mjs +9198 -0
  98. package/lib/scrapers-CVptboMs.mjs +9198 -0
  99. package/lib/scrapers-nhQAvc8d.mjs +9198 -0
  100. package/lib/shell-snapshot-fn-env.sh +67 -43
  101. package/lib/src-BgpusnsB.mjs +5076 -0
  102. package/lib/src-lDmkSRbm.mjs +5006 -0
  103. package/lib/stream-BlldLkVq.mjs +10236 -0
  104. package/lib/stream-CRtexYLe.mjs +10236 -0
  105. package/lib/stream-markup-healing-DMsxBx1L.mjs +491 -0
  106. package/lib/stream-markup-healing-KMkiAA00.mjs +491 -0
  107. package/lib/turndown-DN7ByzEI.mjs +667 -0
  108. package/lib/types/config/settings.d.ts +9 -1
  109. package/lib/types/config/truncate.d.ts +9 -5
  110. package/lib/types/tools/bash/background.d.ts +1 -1
  111. package/lib/types/tools/bash/bash-executor.d.ts +4 -0
  112. package/lib/types/tools/bash/bash-interceptor.d.ts +10 -8
  113. package/lib/types/tools/bash/cd-workdir.d.ts +35 -0
  114. package/lib/types/tools/bash/non-interactive-env.d.ts +2 -0
  115. package/lib/types/tools/bash/render.d.ts +4 -3
  116. package/lib/types/tools/bash/sanitize-text.d.ts +5 -0
  117. package/lib/types/tools/bash/shell-snapshot.d.ts +4 -1
  118. package/lib/types/tools/bash/shell-tokenize.d.ts +60 -0
  119. package/lib/types/tools/bash/streaming-output.d.ts +12 -212
  120. package/lib/types/tools/bash/which.d.ts +20 -0
  121. package/lib/types/tools/bash/windows-shell.d.ts +31 -0
  122. package/lib/types/tools/edit/adapter/index.d.ts +25 -3
  123. package/lib/types/tools/edit/adapter/tools/auto-generated-guard.d.ts +7 -17
  124. package/lib/types/tools/glob/adapter/index.d.ts +27 -1
  125. package/lib/types/tools/grep/adapter/index.d.ts +29 -1
  126. package/lib/types/tools/omp/edit/dispatch.d.ts +43 -0
  127. package/lib/types/tools/omp/edit/hashline/noop-loop-guard.d.ts +3 -1
  128. package/lib/types/tools/omp/tools/auto-generated-guard.d.ts +20 -0
  129. package/lib/types/tools/omp/tools/write.d.ts +4 -4
  130. package/lib/types/tools/shared/auto-generated-guard.dsh.d.ts +16 -4
  131. package/lib/types/tools/shared/bun-shim.d.ts +4 -0
  132. package/lib/types/tools/shared/fs-promises-shim.d.ts +3 -1
  133. package/lib/types/tools/shared/omp-prompt.d.ts +13 -7
  134. package/lib/types/tools/shared/session-state.d.ts +9 -3
  135. package/lib/utils-Bw139LTz.mjs +8119 -0
  136. package/lib/utils-CZw4tVZQ.mjs +12315 -0
  137. package/lib/utils-CiGN7OHR.mjs +12315 -0
  138. package/lib/utils-Cp76gCyk.mjs +8119 -0
  139. package/lib/utils-D0GRDgAr.mjs +12315 -0
  140. package/lib/utils-D2YknRk4.mjs +51 -0
  141. package/lib/utils-DzXZDfYc.mjs +51 -0
  142. package/lib/vision-guard-01qSwFpf.mjs +92 -0
  143. package/lib/vision-guard-DzZGaOga.mjs +92 -0
  144. package/lib/wafer-CgtPHerf.mjs +17 -0
  145. package/lib/wafer-cBtrn6q9.mjs +17 -0
  146. package/lib/xai-oauth-Buk8XVvM.mjs +446 -0
  147. package/lib/xai-oauth-CMal8l1O.mjs +446 -0
  148. package/lib/xiaomi-CZgwvX2G.mjs +157 -0
  149. package/lib/xiaomi-D2qT5cAC.mjs +157 -0
  150. package/lib/zai-7DWMkEqM.mjs +203 -0
  151. package/lib/zai-C59YnomG.mjs +203 -0
  152. package/lib/zip-Cg9ANN_U.mjs +1316 -0
  153. package/lib/zip-D36ZKsP8.mjs +1316 -0
  154. package/package.json +1 -1
  155. package/presets/package.json +1 -1
@@ -0,0 +1,5006 @@
1
+ import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
+ __installBunShim();
3
+ import { n as fs_promises_shim_exports } from "./fs-promises-shim-B-9cMxz5.mjs";
4
+ import * as path from "node:path";
5
+ import * as fs from "node:fs";
6
+ import { Process } from "@oh-my-pi/pi-natives";
7
+ import * as os from "node:os";
8
+ import assert from "node:assert/strict";
9
+ import { parse, stringify } from "yaml";
10
+ import * as fsp from "fs/promises";
11
+ import * as crypto$1 from "node:crypto";
12
+ import { createHash, randomUUID } from "node:crypto";
13
+ import { gzipSync } from "node:zlib";
14
+ import JSON5 from "json5";
15
+ import { AsyncLocalStorage } from "node:async_hooks";
16
+ import { isPromise } from "node:util/types";
17
+ import inspector from "node:inspector";
18
+ import { isMainThread } from "node:worker_threads";
19
+ import * as tls from "node:tls";
20
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/abortable.ts
21
+ var AbortError$1 = class extends Error {
22
+ constructor(signal) {
23
+ assert(signal.aborted, "Abort signal must be aborted");
24
+ const message = signal.reason instanceof Error ? signal.reason.message : "Cancelled";
25
+ super(`Aborted: ${message}`, { cause: signal.reason });
26
+ this.name = "AbortError";
27
+ }
28
+ };
29
+ /**
30
+ * Abortable async iteration over a {@link ReadableStream}. Reads the source
31
+ * reader directly and yields each chunk, so the consumer's `for await` drives a
32
+ * single read loop with no intermediate stream or per-chunk enqueue.
33
+ *
34
+ * Unlike `stream.pipeThrough(..., { signal })`, this explicitly cancels the
35
+ * source reader on abort or early `break`, propagating HTTP-client disconnects
36
+ * and watchdog timeouts to the backend request instead of only stopping the
37
+ * local consumer. On abort it throws {@link AbortError}; the lock is released
38
+ * on completion, abort, throw, or early exit. The source is cancelled only on
39
+ * abort or early exit — never on natural EOF.
40
+ */
41
+ async function* abortableSource(stream, signal) {
42
+ if (signal?.aborted) throw new AbortError$1(signal);
43
+ const reader = stream.getReader();
44
+ let onAbort;
45
+ if (signal) {
46
+ onAbort = () => {
47
+ reader.cancel(signal.reason).catch(() => {});
48
+ };
49
+ signal.addEventListener("abort", onAbort, { once: true });
50
+ }
51
+ let completed = false;
52
+ try {
53
+ for (;;) {
54
+ const result = await reader.read();
55
+ if (signal?.aborted) throw new AbortError$1(signal);
56
+ if (result.done) {
57
+ completed = true;
58
+ return;
59
+ }
60
+ yield result.value;
61
+ }
62
+ } finally {
63
+ if (signal && onAbort) signal.removeEventListener("abort", onAbort);
64
+ if (!completed) try {
65
+ await reader.cancel();
66
+ } catch {}
67
+ try {
68
+ reader.releaseLock();
69
+ } catch {}
70
+ }
71
+ }
72
+ /**
73
+ * Runs a promise-returning function (`pr`). If the given AbortSignal is aborted before or during
74
+ * execution, the promise is rejected with a standard error.
75
+ *
76
+ * @param signal - Optional AbortSignal to cancel the operation
77
+ * @param pr - Function returning a promise to run
78
+ * @returns Promise resolving as `pr` would, or rejecting on abort
79
+ */
80
+ function untilAborted(signal, pr) {
81
+ if (!signal) return typeof pr === "function" ? pr() : pr;
82
+ if (signal.aborted) return Promise.reject(new AbortError$1(signal));
83
+ const { promise, resolve, reject } = Promise.withResolvers();
84
+ const onAbort = () => reject(new AbortError$1(signal));
85
+ signal.addEventListener("abort", onAbort, { once: true });
86
+ (async () => {
87
+ try {
88
+ resolve(await (typeof pr === "function" ? pr() : pr));
89
+ } catch (err) {
90
+ reject(err);
91
+ } finally {
92
+ signal.removeEventListener("abort", onAbort);
93
+ }
94
+ })();
95
+ return promise;
96
+ }
97
+ /**
98
+ * Memoizes a function with no arguments, calling it once and caching the result.
99
+ *
100
+ * @param fn - Function to be called once
101
+ * @returns A function that returns the cached result of `fn`
102
+ */
103
+ function once(fn) {
104
+ let store = void 0;
105
+ return () => {
106
+ if (store) return store.value;
107
+ const value = fn();
108
+ store = { value };
109
+ return value;
110
+ };
111
+ }
112
+ //#endregion
113
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/peek-file.ts
114
+ /**
115
+ * Read the first `maxBytes` of a file (offset 0) and pass that slice to `op`.
116
+ *
117
+ * Buffers are reused to avoid allocating on every peek: sync uses one growable
118
+ * `Uint8Array`; async uses a small fixed pool of `Buffer`s with a bounded wait
119
+ * queue, falling back to a fresh allocation when the pool and queue are saturated
120
+ * or when `maxBytes` exceeds the pool slot size.
121
+ */
122
+ /** Async pool slot size; larger peeks allocate ad hoc. */
123
+ const POOLED_BUFFER_SIZE = 512;
124
+ const ASYNC_POOL_SIZE = 10;
125
+ /** Cap waiter queue so heavy concurrency does not queue unbounded; overflow uses alloc. */
126
+ const MAX_ASYNC_WAITERS = 4;
127
+ const INITIAL_SYNC_BUFFER_SIZE = 1024;
128
+ const EMPTY_BUFFER = Buffer.alloc(0);
129
+ const asyncPool = Array.from({ length: ASYNC_POOL_SIZE }, () => Buffer.allocUnsafe(POOLED_BUFFER_SIZE));
130
+ const availableAsyncPoolIndexes = Array.from({ length: ASYNC_POOL_SIZE }, (_, index) => index);
131
+ const asyncPoolWaiters = [];
132
+ new Uint8Array(INITIAL_SYNC_BUFFER_SIZE);
133
+ /** Returns a pool slot index, or `-1` when the caller should use a standalone buffer. */
134
+ function acquireAsyncPoolIndex() {
135
+ const index = availableAsyncPoolIndexes.pop();
136
+ if (index !== void 0) return index;
137
+ if (asyncPoolWaiters.length >= MAX_ASYNC_WAITERS) return -1;
138
+ const { promise, resolve } = Promise.withResolvers();
139
+ asyncPoolWaiters.push(resolve);
140
+ return promise;
141
+ }
142
+ function releaseAsyncPoolIndex(index) {
143
+ if (index < 0) return;
144
+ const waiter = asyncPoolWaiters.shift();
145
+ if (waiter) {
146
+ waiter(index);
147
+ return;
148
+ }
149
+ availableAsyncPoolIndexes.push(index);
150
+ }
151
+ async function withAsyncPoolBuffer(maxBytes, op) {
152
+ if (maxBytes <= 0) return op(EMPTY_BUFFER);
153
+ if (maxBytes > POOLED_BUFFER_SIZE) return op(Buffer.allocUnsafe(maxBytes));
154
+ const poolIndex = await acquireAsyncPoolIndex();
155
+ const buffer = poolIndex >= 0 ? asyncPool[poolIndex] : Buffer.allocUnsafe(maxBytes);
156
+ try {
157
+ return await op(buffer.subarray(0, maxBytes));
158
+ } finally {
159
+ releaseAsyncPoolIndex(poolIndex);
160
+ }
161
+ }
162
+ /**
163
+ * Like {@link peekFileSync} but uses async I/O.
164
+ */
165
+ async function peekFile(filePath, maxBytes, op) {
166
+ if (maxBytes <= 0) return op(EMPTY_BUFFER);
167
+ const fileHandle = await fs.promises.open(filePath, "r");
168
+ try {
169
+ return await withAsyncPoolBuffer(maxBytes, async (buffer) => {
170
+ const { bytesRead } = await fileHandle.read(buffer, 0, buffer.byteLength, 0);
171
+ return op(buffer.subarray(0, bytesRead));
172
+ });
173
+ } finally {
174
+ await fileHandle.close();
175
+ }
176
+ }
177
+ //#endregion
178
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/binary.ts
179
+ /**
180
+ * Content-based binary/text classification for files that are about to be
181
+ * decoded as UTF-8 text and shown to a model or user.
182
+ *
183
+ * The read tool and `@file` auto-read both materialize file bytes as UTF-8
184
+ * strings. For a binary file (font, object, archive, packed blob) that decode
185
+ * is lossy: NUL bytes and invalid sequences survive as control characters and
186
+ * U+FFFD replacements, which corrupt terminal rendering and waste the context
187
+ * window with mojibake. Sniff the header first and refuse instead.
188
+ *
189
+ * @example
190
+ * if (await isProbablyBinary(path)) return "[binary file omitted]";
191
+ * const text = await Bun.file(path).text();
192
+ */
193
+ /** Header window sniffed for the binary heuristic; mirrors git's 8000-byte scan. */
194
+ const BINARY_SNIFF_BYTES = 8192;
195
+ /**
196
+ * Classify an in-memory byte header as binary (non-UTF-8-text).
197
+ *
198
+ * Binary when the header contains a NUL byte (true binary, plus UTF-16/UTF-32
199
+ * text whose ASCII range is NUL-padded) or when it is not valid UTF-8. The
200
+ * decode runs in streaming mode so a multibyte sequence truncated at the header
201
+ * boundary is tolerated, while any genuinely invalid byte still fails — matching
202
+ * the strict `fatal` decode the `local://`/`ssh://` read paths already use.
203
+ */
204
+ function isProbablyBinaryHeader(header) {
205
+ if (header.indexOf(0) !== -1) return true;
206
+ try {
207
+ new TextDecoder("utf-8", { fatal: true }).decode(header, { stream: true });
208
+ return false;
209
+ } catch {
210
+ return true;
211
+ }
212
+ }
213
+ /**
214
+ * Sniff the first {@link BINARY_SNIFF_BYTES} of `filePath` and report whether it
215
+ * is binary (non-UTF-8-text). See {@link isProbablyBinaryHeader} for the rule.
216
+ */
217
+ function isProbablyBinary(filePath, maxBytes = BINARY_SNIFF_BYTES) {
218
+ return peekFile(filePath, maxBytes, isProbablyBinaryHeader);
219
+ }
220
+ //#endregion
221
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/package.json
222
+ var version = "17.3.5";
223
+ var engines = { "bun": ">=1.3.14" };
224
+ /** Version (e.g. "1.0.0") */
225
+ const VERSION = version;
226
+ /** Default User-Agent header string (e.g. "omp/17.2.12") */
227
+ const USER_AGENT = `omp/${VERSION}`;
228
+ engines.bun.replace(/[^0-9.]/g, "");
229
+ const PROFILE_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
230
+ /**
231
+ * Names Windows treats as reserved device aliases. Matches the basename
232
+ * itself as well as any `BASENAME.<anything>` form, because Windows reserves
233
+ * `CON.foo`/`PRN.txt`/etc. too — using them as a profile name would let
234
+ * `setProfile` accept the input only for directory creation to fail later
235
+ * with a confusing `ENOENT`/`EINVAL`. Case-insensitive: NTFS treats `CON`
236
+ * and `con` identically.
237
+ */
238
+ const WINDOWS_RESERVED_BASENAME_RE = /^(?:CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])(?:\..*)?$/i;
239
+ /**
240
+ * Normalize and validate a profile name. Returns `undefined` for the implicit
241
+ * default (empty string, whitespace, or the explicit "default" sentinel) and
242
+ * throws for syntactically invalid or platform-reserved names.
243
+ *
244
+ * Exported so consumers of `@oh-my-pi/pi-utils/dirs` (CLI bootstrap, tests,
245
+ * downstream tools) can validate user input without re-deriving the rules.
246
+ */
247
+ function normalizeProfileName(profile) {
248
+ const normalized = profile?.trim();
249
+ if (!normalized || normalized === "default") return void 0;
250
+ if (normalized === "." || normalized === ".." || normalized.endsWith(".") || !PROFILE_NAME_RE.test(normalized) || WINDOWS_RESERVED_BASENAME_RE.test(normalized)) throw new Error(`Invalid OMP profile "${profile}". Profile names must match ${PROFILE_NAME_RE.source}, cannot be "." or "..", cannot end with ".", and cannot be a Windows reserved device name (CON, PRN, AUX, NUL, COM0-9, LPT0-9, or any of those with an extension).`);
251
+ return normalized;
252
+ }
253
+ /**
254
+ * Resolve the active profile from the two profile env vars. `OMP_PROFILE` is the
255
+ * canonical variable and takes precedence; `PI_PROFILE` is the legacy
256
+ * compatibility fallback, consulted only when `OMP_PROFILE` is undefined. An
257
+ * explicitly-empty `OMP_PROFILE` therefore selects the default profile rather
258
+ * than silently inheriting `PI_PROFILE`. Delegates validation/normalization to
259
+ * {@link normalizeProfileName} (which throws on a syntactically invalid value).
260
+ */
261
+ function resolveProfileEnv(omp, pi) {
262
+ return normalizeProfileName(omp !== void 0 ? omp : pi);
263
+ }
264
+ function getProfileFromEnv() {
265
+ return resolveProfileEnv(process.env.OMP_PROFILE, process.env.PI_PROFILE);
266
+ }
267
+ /**
268
+ * Module-load profile resolution. Unlike {@link getProfileFromEnv}, an invalid
269
+ * OMP_PROFILE/PI_PROFILE value does NOT throw here — a bad env var must not
270
+ * crash a bare `import` of this module with an uncaught stack trace before the
271
+ * CLI's error handling is in scope. The default profile is used instead; the
272
+ * CLI re-validates the env (see `runCli` in coding-agent/src/cli.ts) so the
273
+ * user still gets a clean "Invalid OMP profile" message.
274
+ */
275
+ function readProfileFromEnvSafe() {
276
+ try {
277
+ return getProfileFromEnv();
278
+ } catch {
279
+ return;
280
+ }
281
+ }
282
+ function getBaseConfigRoot() {
283
+ return path.join(os.homedir(), getConfigDirName());
284
+ }
285
+ function getProfileConfigRoot(profile) {
286
+ const root = getBaseConfigRoot();
287
+ return profile ? path.join(root, "profiles", profile) : root;
288
+ }
289
+ function readPiProfileFromEnvSafe() {
290
+ try {
291
+ return normalizeProfileName(process.env.PI_PROFILE);
292
+ } catch {
293
+ return;
294
+ }
295
+ }
296
+ function getProfileAgentDir(profile) {
297
+ return path.join(getProfileConfigRoot(profile), "agent");
298
+ }
299
+ function isProfileDerivedAgentDir(profile, agentDirEnv) {
300
+ return profile !== void 0 && agentDirEnv === getProfileAgentDir(profile);
301
+ }
302
+ /**
303
+ * On macOS, strip /private prefix only when both paths resolve to the same location.
304
+ * This preserves aliases like /private/tmp -> /tmp without rewriting unrelated paths.
305
+ */
306
+ function standardizeMacOSPath(p) {
307
+ if (process.platform !== "darwin" || !p.startsWith("/private/")) return p;
308
+ const stripped = p.slice(8);
309
+ try {
310
+ if (fs.realpathSync(p) === fs.realpathSync(stripped)) return stripped;
311
+ } catch {}
312
+ return p;
313
+ }
314
+ standardizeMacOSPath(process.cwd());
315
+ /** Get the config directory name relative to home (e.g. ".omp" or PI_CONFIG_DIR override). */
316
+ function getConfigDirName() {
317
+ return process.env.PI_CONFIG_DIR || ".omp";
318
+ }
319
+ /**
320
+ * Resolves and caches all omp directory paths. On Linux, when XDG environment
321
+ * variables are set, paths are redirected under $XDG_*_HOME/omp/. A new
322
+ * instance is created whenever the agent directory changes, which naturally
323
+ * invalidates all cached paths.
324
+ */
325
+ var DirResolver = class {
326
+ configRoot;
327
+ agentDir;
328
+ #rootDirs;
329
+ #agentDirs;
330
+ #rootCache = /* @__PURE__ */ new Map();
331
+ #agentCache = /* @__PURE__ */ new Map();
332
+ constructor(options = {}) {
333
+ const profile = normalizeProfileName(options.profile);
334
+ this.configRoot = getProfileConfigRoot(profile);
335
+ const defaultAgent = path.join(this.configRoot, "agent");
336
+ const agentDirOverride = profile ? void 0 : options.agentDirOverride;
337
+ this.agentDir = agentDirOverride ? path.resolve(agentDirOverride) : defaultAgent;
338
+ const isDefault = this.agentDir === defaultAgent;
339
+ let xdgData;
340
+ let xdgState;
341
+ let xdgCache;
342
+ if ((process.platform === "linux" || process.platform === "darwin") && isDefault) {
343
+ const resolveIf = (envVar) => {
344
+ const value = process.env[envVar];
345
+ if (!value) return void 0;
346
+ try {
347
+ const appRoot = path.join(value, "omp");
348
+ if (profile) {
349
+ const profilePath = path.join(appRoot, "profiles", profile);
350
+ if (fs.existsSync(profilePath)) return profilePath;
351
+ return;
352
+ }
353
+ if (fs.existsSync(appRoot)) return appRoot;
354
+ } catch {}
355
+ };
356
+ xdgData = resolveIf("XDG_DATA_HOME");
357
+ xdgState = resolveIf("XDG_STATE_HOME");
358
+ xdgCache = resolveIf("XDG_CACHE_HOME");
359
+ }
360
+ this.#rootDirs = {
361
+ data: xdgData ?? this.configRoot,
362
+ state: xdgState ?? this.configRoot,
363
+ cache: xdgCache ?? this.configRoot
364
+ };
365
+ this.#agentDirs = {
366
+ data: xdgData ?? this.agentDir,
367
+ state: xdgState ?? this.agentDir,
368
+ cache: xdgCache ?? this.agentDir
369
+ };
370
+ }
371
+ /** Config-root subdirectory, with optional XDG override. */
372
+ rootSubdir(subdir, xdg) {
373
+ const cached = this.#rootCache.get(subdir);
374
+ if (cached) return cached;
375
+ const base = xdg ? this.#rootDirs[xdg] : this.configRoot;
376
+ const result = path.join(base, subdir);
377
+ this.#rootCache.set(subdir, result);
378
+ return result;
379
+ }
380
+ /** Agent subdirectory, with optional XDG override. */
381
+ agentSubdir(userAgentDir, subdir, xdg) {
382
+ if (!userAgentDir || userAgentDir === this.agentDir) {
383
+ const cached = this.#agentCache.get(subdir);
384
+ if (cached) return cached;
385
+ const base = xdg ? this.#agentDirs[xdg] : this.agentDir;
386
+ const result = path.join(base, subdir);
387
+ this.#agentCache.set(subdir, result);
388
+ return result;
389
+ }
390
+ return path.join(userAgentDir, subdir);
391
+ }
392
+ };
393
+ /**
394
+ * Decide which `PI_CODING_AGENT_DIR` value to capture as the pre-profile
395
+ * baseline. A value equal to a profile's derived agent dir is profile-derived
396
+ * (propagated by a parent's `setProfile`), so it must NOT be snapshotted as the
397
+ * default-mode baseline — otherwise default mode would resolve to the profile's
398
+ * agent dir. The profile source can be the active profile or a lower-priority
399
+ * `PI_PROFILE` that was bypassed because `OMP_PROFILE` explicitly selected the
400
+ * default profile. Returns `undefined` in those cases so reset falls back to the
401
+ * standard `~/.omp/agent`.
402
+ */
403
+ function resolvePreProfileAgentDir(profile, agentDirEnv, profileAgentDirSource = profile) {
404
+ return isProfileDerivedAgentDir(profile ?? profileAgentDirSource, agentDirEnv) ? void 0 : agentDirEnv;
405
+ }
406
+ let activeProfile = readProfileFromEnvSafe();
407
+ /**
408
+ * Resolve the agent-dir override for the current `activeProfile` from the live
409
+ * environment. A named profile derives its own agent dir (no override); default
410
+ * mode honors a non-profile `PI_CODING_AGENT_DIR` (see
411
+ * {@link resolvePreProfileAgentDir}). Shared by the module-load resolver and
412
+ * {@link refreshDirsFromEnv} so both apply identical logic.
413
+ */
414
+ function resolveActiveAgentDirOverride() {
415
+ return activeProfile ? void 0 : resolvePreProfileAgentDir(void 0, process.env.PI_CODING_AGENT_DIR, readPiProfileFromEnvSafe());
416
+ }
417
+ let dirs = new DirResolver({
418
+ agentDirOverride: resolveActiveAgentDirOverride(),
419
+ profile: activeProfile
420
+ });
421
+ resolvePreProfileAgentDir(activeProfile, process.env.PI_CODING_AGENT_DIR, activeProfile ?? readPiProfileFromEnvSafe());
422
+ os.homedir();
423
+ /**
424
+ * Rebuild the dirs resolver from the current environment, reusing the profile
425
+ * resolved at module load. Directory-affecting keys (XDG_*_HOME and, in default
426
+ * mode, `PI_CODING_AGENT_DIR`) loaded from a profile/agent `.env` only reach
427
+ * `process.env` *after* this module froze the resolver at import time, so
428
+ * `env.ts` calls this once after applying its `.env` files. The agent `.env`
429
+ * location derives from the profile name + home before this runs, so the
430
+ * rebuild re-reads only the directory vars, never the profile selection. The
431
+ * `preProfileAgentDirEnv` snapshot is intentionally left untouched.
432
+ */
433
+ function refreshDirsFromEnv() {
434
+ dirs = new DirResolver({
435
+ agentDirOverride: resolveActiveAgentDirOverride(),
436
+ profile: activeProfile
437
+ });
438
+ }
439
+ /** Get the config root directory (~/.omp). */
440
+ function getConfigRootDir() {
441
+ return dirs.configRoot;
442
+ }
443
+ /** Get the agent config directory (~/.omp/agent). */
444
+ function getAgentDir() {
445
+ return dirs.agentDir;
446
+ }
447
+ /** Get the logs directory (~/.omp/logs). */
448
+ function getLogsDir() {
449
+ return dirs.rootSubdir("logs", "state");
450
+ }
451
+ /** Get the remote mount directory (~/.omp/remote). */
452
+ function getRemoteDir() {
453
+ return dirs.rootSubdir("remote", "data");
454
+ }
455
+ /** Get DOCS_RS cache directory () */
456
+ function getDocsRsCacheDir() {
457
+ return dirs.rootSubdir("webcache", "cache");
458
+ }
459
+ /** Get the auto-QA grievances SQLite database path (~/.omp/autoqa.db; XDG: $XDG_DATA_HOME/omp/autoqa.db). */
460
+ function getAutoQaDbPath() {
461
+ return dirs.rootSubdir("autoqa.db", "data");
462
+ }
463
+ /** Get the document conversion cache directory (~/.omp/agent/cache/document-conversions; XDG default: $XDG_CACHE_HOME/omp/cache/document-conversions). */
464
+ function getDocumentConversionCacheDir(agentDir) {
465
+ return dirs.agentSubdir(agentDir, path.join("cache", "document-conversions"), "cache");
466
+ }
467
+ /** Get the tools directory (~/.omp/agent/tools). */
468
+ function getToolsDir(agentDir) {
469
+ return dirs.agentSubdir(agentDir, "tools");
470
+ }
471
+ /** Get the debug log path (~/.omp/agent/omp-debug.log). */
472
+ function getDebugLogPath(agentDir) {
473
+ return dirs.agentSubdir(agentDir, `omp-debug.log`, "state");
474
+ }
475
+ let cachedInstallId = null;
476
+ const INSTALL_ID_FILE = "install-id";
477
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
478
+ /**
479
+ * Persistent per-install UUID stored at `~/.omp/install-id`.
480
+ *
481
+ * Generated lazily on first call and persisted with `O_CREAT|O_EXCL` so
482
+ * concurrent first-call races don't clobber each other (loser re-reads the
483
+ * winner's id). Survives independently of agent state: deleting
484
+ * `~/.omp/agent/` does not regenerate it. Server-side dedup for grievance
485
+ * pushes (and similar telemetry) keys on this id.
486
+ *
487
+ * Anchored to the base config root (`~/.omp/install-id`) regardless of the
488
+ * active profile: install identity is per-install, not per-profile, so every
489
+ * profile shares one id and the global cache stays correct no matter the
490
+ * profile / `getInstallId` call order.
491
+ */
492
+ function getInstallId() {
493
+ if (cachedInstallId) return cachedInstallId;
494
+ const filePath = path.join(getBaseConfigRoot(), INSTALL_ID_FILE);
495
+ let observedInvalid = false;
496
+ try {
497
+ const existing = fs.readFileSync(filePath, "utf8").trim();
498
+ if (UUID_RE.test(existing)) {
499
+ cachedInstallId = existing;
500
+ return existing;
501
+ }
502
+ observedInvalid = existing.length > 0;
503
+ } catch {}
504
+ const next = crypto.randomUUID();
505
+ try {
506
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
507
+ if (observedInvalid) try {
508
+ fs.unlinkSync(filePath);
509
+ } catch {}
510
+ const fd = fs.openSync(filePath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 384);
511
+ try {
512
+ fs.writeSync(fd, `${next}\n`);
513
+ } finally {
514
+ fs.closeSync(fd);
515
+ }
516
+ } catch (err) {
517
+ if (err.code === "EEXIST") try {
518
+ const existing = fs.readFileSync(filePath, "utf8").trim();
519
+ if (UUID_RE.test(existing)) {
520
+ cachedInstallId = existing;
521
+ return existing;
522
+ }
523
+ } catch {}
524
+ }
525
+ cachedInstallId = next;
526
+ return next;
527
+ }
528
+ //#endregion
529
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/env.ts
530
+ const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
531
+ /**
532
+ * Strict shell-identifier shape. Used for dotenv keys we accept into
533
+ * `Bun.env` — those should be referenceable as `$NAME` from POSIX shells,
534
+ * so we reject anything outside `[A-Za-z_][A-Za-z0-9_]*`.
535
+ */
536
+ function isValidEnvName(name) {
537
+ return ENV_NAME_RE.test(name);
538
+ }
539
+ /**
540
+ * The only names that are genuinely unsafe to forward to a native `execve`
541
+ * spawn: empty, containing `=` (would corrupt the `KEY=VALUE` framing) or
542
+ * NUL (terminates the C string mid-entry). Windows ships standard variables
543
+ * whose names contain parentheses (e.g. `ProgramFiles(x86)`, `CommonProgramFiles(x86)`)
544
+ * — those MUST survive the scrub so downstream resolvers (Git Bash discovery
545
+ * in `procmgr.ts`, etc.) can still read them.
546
+ */
547
+ function isSafeEnvName(name) {
548
+ return name.length > 0 && !name.includes("=") && !name.includes("\0");
549
+ }
550
+ function isSafeEnvValue(value) {
551
+ return !value.includes("\0");
552
+ }
553
+ function isMacosMallocStackLoggingEnvName(name) {
554
+ return name === "MallocStackLogging" || name === "MallocStackLoggingNoCompact";
555
+ }
556
+ function readLaunchEnv() {
557
+ if (process.platform === "linux") try {
558
+ const values = /* @__PURE__ */ new Map();
559
+ for (const entry of fs.readFileSync("/proc/self/environ", "utf8").split("\0")) {
560
+ const separator = entry.indexOf("=");
561
+ if (separator > 0) values.set(entry.slice(0, separator), entry.slice(separator + 1));
562
+ }
563
+ return values;
564
+ } catch {}
565
+ if (!process.execArgv.includes("--no-env-file")) return void 0;
566
+ const values = /* @__PURE__ */ new Map();
567
+ for (const key in Bun.env) {
568
+ const value = Bun.env[key];
569
+ if (value !== void 0) values.set(key, value);
570
+ }
571
+ return values;
572
+ }
573
+ readLaunchEnv();
574
+ const projectEnvNamesLoadedByOmp = /* @__PURE__ */ new Set();
575
+ /**
576
+ * Parse one dotenv line with Bun-compatible semantics: an optional `export`
577
+ * prefix, full-line `#` comments, inline `#` comments after whitespace on
578
+ * unquoted values, and single/double/backtick quoting (a `#` inside quotes
579
+ * stays literal). Returns undefined for blank lines, comments, and malformed
580
+ * names.
581
+ */
582
+ function parseEnvLine(line) {
583
+ const trimmed = line.trim();
584
+ if (!trimmed || trimmed.startsWith("#")) return void 0;
585
+ const eqIndex = trimmed.indexOf("=");
586
+ if (eqIndex === -1) return void 0;
587
+ let key = trimmed.slice(0, eqIndex).trim();
588
+ const exported = key.match(/^export[ \t]+(.*)$/);
589
+ if (exported) key = exported[1].trim();
590
+ if (!isValidEnvName(key)) return void 0;
591
+ const raw = trimmed.slice(eqIndex + 1).replace(/^[ \t]+/, "");
592
+ const quote = raw[0];
593
+ if (quote === "\"" || quote === "'" || quote === "`") {
594
+ let close = raw.indexOf(quote, 1);
595
+ while (close !== -1 && raw[close - 1] === "\\") close = raw.indexOf(quote, close + 1);
596
+ return {
597
+ key,
598
+ value: close === -1 ? raw.slice(1) : raw.slice(1, close)
599
+ };
600
+ }
601
+ const commentIndex = raw.search(/[ \t]#/);
602
+ return {
603
+ key,
604
+ value: (commentIndex === -1 ? raw : raw.slice(0, commentIndex)).trimEnd()
605
+ };
606
+ }
607
+ /**
608
+ * Parses a .env file synchronously into key-value string pairs using
609
+ * {@link parseEnvLine} for Bun-compatible line semantics, then mirrors valid
610
+ * `OMP_` variables to their `PI_` aliases.
611
+ */
612
+ function parseEnvFile(filePath) {
613
+ const result = {};
614
+ try {
615
+ const content = fs.readFileSync(filePath, "utf-8");
616
+ for (const line of content.split("\n")) {
617
+ const parsed = parseEnvLine(line);
618
+ if (parsed && isSafeEnvValue(parsed.value)) result[parsed.key] = parsed.value;
619
+ }
620
+ } catch {}
621
+ for (const k in result) if (k.startsWith("OMP_")) result[`PI_${k.slice(4)}`] = result[k];
622
+ return result;
623
+ }
624
+ const homeEnv = parseEnvFile(path.join(os.homedir(), ".env"));
625
+ const piEnv = parseEnvFile(path.join(getConfigRootDir(), ".env"));
626
+ const agentEnv = parseEnvFile(path.join(getAgentDir(), ".env"));
627
+ const projectEnv = parseEnvFile(path.join(process.cwd(), ".env"));
628
+ for (const key of Object.keys(Bun.env)) {
629
+ const value = Bun.env[key];
630
+ if (!isSafeEnvName(key) || isMacosMallocStackLoggingEnvName(key) || value === void 0 || !isSafeEnvValue(value)) delete Bun.env[key];
631
+ }
632
+ for (const file of [
633
+ projectEnv,
634
+ agentEnv,
635
+ piEnv,
636
+ homeEnv
637
+ ]) for (const key in file) if (!isMacosMallocStackLoggingEnvName(key) && !Bun.env[key]) {
638
+ Bun.env[key] = file[key];
639
+ if (file === projectEnv) projectEnvNamesLoadedByOmp.add(key);
640
+ }
641
+ refreshDirsFromEnv();
642
+ /**
643
+ * Intentional re-export of Bun.env.
644
+ *
645
+ * All users should import this env module (import { $env } from "@oh-my-pi/pi-utils")
646
+ * before using environment variables. This ensures that .env files have been loaded and
647
+ * overrides (project, home) have been applied, so $env always reflects the correct values.
648
+ */
649
+ const $env = Bun.env;
650
+ /**
651
+ * Resolve the first environment variable value from the given keys.
652
+ * @param keys - The keys to resolve.
653
+ * @returns The first environment variable value, or undefined if no value is found.
654
+ */
655
+ function $pickenv(...keys) {
656
+ for (const key of keys) {
657
+ const value = Bun.env[key]?.trim();
658
+ if (value) return value;
659
+ }
660
+ }
661
+ /**
662
+ * Parses a positive decimal integer from `$env[name]`.
663
+ * Empty, invalid, NaN, zero, or negative values return `defaultValue`.
664
+ */
665
+ function $envpos(name, defaultValue) {
666
+ const raw = $env[name];
667
+ if (!raw) return defaultValue;
668
+ const parsed = Number.parseInt(raw, 10);
669
+ if (Number.isNaN(parsed) || parsed <= 0) return defaultValue;
670
+ return parsed;
671
+ }
672
+ const BUN_TEST_ENTRY_PATTERN = /[._](?:test|spec)\.[cm]?[jt]sx?$/;
673
+ /** True when the process is an explicitly marked test child or Bun is running a test entrypoint. */
674
+ function isBunTestRuntime() {
675
+ if (Bun.env.PI_TEST_RUNTIME === "1") return true;
676
+ return (Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test") && BUN_TEST_ENTRY_PATTERN.test(Bun.main);
677
+ }
678
+ let terminalHeadless = isBunTestRuntime();
679
+ /**
680
+ * True when real-terminal side effects must be suppressed: stdout escape/frame
681
+ * writes, stdin raw-mode + resume, CSI/OSC capability probes, SIGWINCH, window
682
+ * title changes, and emergency restore. Defaults to {@link isBunTestRuntime} so
683
+ * `bun test` launched inside a real TTY never paints the TUI, leaks probe
684
+ * queries, or hijacks the developer's stdin; production runtimes stay
685
+ * interactive.
686
+ *
687
+ * Terminal-contract tests that must exercise the real I/O path opt out with
688
+ * `setTerminalHeadless(false)` and restore it afterwards.
689
+ */
690
+ function isTerminalHeadless() {
691
+ return terminalHeadless;
692
+ }
693
+ const TRUTHY = {
694
+ "1": true,
695
+ Y: true,
696
+ y: true,
697
+ TRUE: true,
698
+ true: true,
699
+ YES: true,
700
+ yes: true,
701
+ ON: true,
702
+ on: true
703
+ };
704
+ /** Parse a boolean-ish env value ("1", "yes", "on", …); `def` when unset/empty. */
705
+ function parseFlag(value, def = false) {
706
+ if (!value) return def;
707
+ return TRUTHY[value] === true;
708
+ }
709
+ function $flag(name, def = false) {
710
+ return parseFlag($env[name], def);
711
+ }
712
+ /**
713
+ * Format a number with K/M/B suffix for compact display.
714
+ * Uses 1 decimal for small leading digits when non-zero, rounded otherwise.
715
+ * Examples: "999", "1K", "1.5K", "25K", "1M", "1.5M", "25M", "1.5B"
716
+ */
717
+ function formatNumber(n) {
718
+ if (n < 1e3) return n.toString();
719
+ if (n < 1e4) return `${trim1(n / 1e3)}K`;
720
+ if (n < 1e6) return `${Math.round(n / 1e3)}K`;
721
+ if (n < 1e7) return `${trim1(n / 1e6)}M`;
722
+ if (n < 1e9) return `${Math.round(n / 1e6)}M`;
723
+ if (n < 1e10) return `${trim1(n / 1e9)}B`;
724
+ return `${Math.round(n / 1e9)}B`;
725
+ }
726
+ /** Format with up to 1 decimal place, dropping trailing `.0`. */
727
+ function trim1(n) {
728
+ const s = n.toFixed(1);
729
+ return s.endsWith(".0") ? s.slice(0, -2) : s;
730
+ }
731
+ /**
732
+ * Format a byte count to a human-readable string.
733
+ * Examples: "512B", "1.5KB", "2.3MB", "1.2GB"
734
+ */
735
+ function formatBytes(bytes) {
736
+ if (bytes < 1024) return `${bytes}B`;
737
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)}KB`;
738
+ if (bytes < 1073741824) return `${(bytes / 1048576).toFixed(1)}MB`;
739
+ return `${(bytes / 1073741824).toFixed(1)}GB`;
740
+ }
741
+ /**
742
+ * Truncate a string to maxLen characters, appending an ellipsis if truncated.
743
+ * For display-width-aware truncation (terminals), use truncateToWidth from @oh-my-pi/pi-tui.
744
+ */
745
+ function truncate(str, maxLen, ellipsis = "…") {
746
+ if (str.length <= maxLen) return str;
747
+ const sliceLen = Math.max(0, maxLen - ellipsis.length);
748
+ return `${str.slice(0, sliceLen)}${ellipsis}`;
749
+ }
750
+ /**
751
+ * Format count with pluralized label (e.g., "3 files", "1 error").
752
+ */
753
+ function formatCount(label, count) {
754
+ const safeCount = Number.isFinite(count) ? count : 0;
755
+ return `${safeCount} ${pluralize(label, safeCount)}`;
756
+ }
757
+ /**
758
+ * Format age from seconds to human-readable string.
759
+ */
760
+ function formatAge(ageSeconds) {
761
+ if (!ageSeconds) return "";
762
+ const mins = Math.floor(ageSeconds / 60);
763
+ const hours = Math.floor(mins / 60);
764
+ const days = Math.floor(hours / 24);
765
+ const weeks = Math.floor(days / 7);
766
+ const months = Math.floor(days / 30);
767
+ if (months > 0) return `${months}mo ago`;
768
+ if (weeks > 0) return `${weeks}w ago`;
769
+ if (days > 0) return `${days}d ago`;
770
+ if (hours > 0) return `${hours}h ago`;
771
+ if (mins > 0) return `${mins}m ago`;
772
+ return "just now";
773
+ }
774
+ /**
775
+ * Pluralize a label based on the count.
776
+ */
777
+ function pluralize(label, count) {
778
+ if (count === 1) return label;
779
+ if (/(?:ch|sh|s|x|z)$/i.test(label)) return `${label}es`;
780
+ if (/[^aeiou]y$/i.test(label)) return `${label.slice(0, -1)}ies`;
781
+ return `${label}s`;
782
+ }
783
+ //#endregion
784
+ //#region src/tools/shared/bun-shim.ts
785
+ /**
786
+ * DSH Bun compatibility shim — installs a Node implementation of the `Bun`
787
+ * global that OMP tool code (copied verbatim into adapter/omp) uses.
788
+ *
789
+ * OMP is a Bun project; its code calls `Bun.file`, `Bun.write`, `Bun.hash`,
790
+ * `Bun.stringWidth`, `Bun.Glob`, `Bun.Archive`, `Bun.FileSink`, … This module
791
+ * provides those APIs on Node 22, plus the `Buffer`/`Uint8Array` `toBase64`/
792
+ * `fromBase64` extensions Bun adds. Every change here is recorded in step.md
793
+ * ("Bun 兼容" section).
794
+ *
795
+ * `Bun.Image` is backed by `sharp` resolved at runtime (拍板#22, 2026-08-28:
796
+ * 图像链路重建——sharp 兼容层取代构造即抛错的桩,fetch/read 的 resizeImage
797
+ * 后端由此复活)。sharp 是宿主 DSH 的传递依赖(dsh-attachment-local 直接依
798
+ * 赖),本包不打包、不安装,运行时经 createRequire/import 级联解析;解析不到
799
+ * 时抛带指引的错误,`resizeImage` 的 try/catch 降级语义保持不变。
800
+ */
801
+ function patchBase64() {
802
+ const bufProto = Buffer.prototype;
803
+ const u8Proto = Uint8Array.prototype;
804
+ if (typeof bufProto.toBase64 !== "function") bufProto.toBase64 = function() {
805
+ return this.toString("base64");
806
+ };
807
+ if (typeof bufProto.fromBase64 !== "function") Buffer.fromBase64 = function(s) {
808
+ return Buffer.from(s, "base64");
809
+ };
810
+ if (typeof u8Proto.toBase64 !== "function") u8Proto.toBase64 = function() {
811
+ return Buffer.from(this.buffer, this.byteOffset, this.byteLength).toString("base64");
812
+ };
813
+ if (typeof u8Proto.fromBase64 !== "function") u8Proto.fromBase64 = function(s) {
814
+ return new Uint8Array(Buffer.from(s, "base64"));
815
+ };
816
+ }
817
+ var BunFileShim = class {
818
+ path;
819
+ constructor(p) {
820
+ this.path = p;
821
+ }
822
+ get size() {
823
+ try {
824
+ return fs.statSync(this.path).size;
825
+ } catch {
826
+ return 0;
827
+ }
828
+ }
829
+ get name() {
830
+ return path.basename(this.path);
831
+ }
832
+ async exists() {
833
+ try {
834
+ await fsp.stat(this.path);
835
+ return true;
836
+ } catch {
837
+ return false;
838
+ }
839
+ }
840
+ async text() {
841
+ return fsp.readFile(this.path, "utf8");
842
+ }
843
+ async json() {
844
+ return JSON.parse(await this.text());
845
+ }
846
+ async arrayBuffer() {
847
+ const b = await fsp.readFile(this.path);
848
+ return b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
849
+ }
850
+ async bytes() {
851
+ return fsp.readFile(this.path);
852
+ }
853
+ writer() {
854
+ return new FileSinkShim(this.path);
855
+ }
856
+ /** Bun extension: stat the file (patched Node implementation). */
857
+ async stat() {
858
+ const st = await fsp.stat(this.path);
859
+ return {
860
+ mtimeMs: st.mtimeMs,
861
+ size: st.size,
862
+ isDirectory: () => st.isDirectory()
863
+ };
864
+ }
865
+ /** Bun extension: delete the file. */
866
+ async unlink() {
867
+ await fsp.unlink(this.path);
868
+ }
869
+ /** Bun extension: write content to the file (overwrite). */
870
+ async write(content) {
871
+ await bunWrite(this.path, content);
872
+ }
873
+ /** Bun extension: a sub-range view of the file. */
874
+ slice(start, end) {
875
+ return new BunFileSliceShim(this.path, start ?? 0, end);
876
+ }
877
+ stream() {
878
+ return new ReadableStream({ start: async (controller) => {
879
+ controller.enqueue(await fsp.readFile(this.path));
880
+ controller.close();
881
+ } });
882
+ }
883
+ };
884
+ /** Sub-range view of a file (Bun's `BunFile.slice`). */
885
+ var BunFileSliceShim = class extends BunFileShim {
886
+ #start;
887
+ #end;
888
+ constructor(filePath, start, end) {
889
+ super(filePath);
890
+ this.#start = start;
891
+ this.#end = end;
892
+ }
893
+ get size() {
894
+ const total = super.size;
895
+ return Math.max(0, (this.#end ?? total) - this.#start);
896
+ }
897
+ async #sliced() {
898
+ return (await fsp.readFile(this.path)).subarray(this.#start, this.#end);
899
+ }
900
+ async text() {
901
+ return new TextDecoder("utf-8", { fatal: false }).decode(await this.#sliced());
902
+ }
903
+ async bytes() {
904
+ return this.#sliced();
905
+ }
906
+ async arrayBuffer() {
907
+ const b = await this.#sliced();
908
+ return b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
909
+ }
910
+ };
911
+ var FileSinkShim = class {
912
+ #fd = null;
913
+ #filePath;
914
+ constructor(filePath) {
915
+ this.#filePath = filePath;
916
+ }
917
+ #open() {
918
+ fs.mkdirSync(path.dirname(this.#filePath), { recursive: true });
919
+ this.#fd = fs.openSync(this.#filePath, "a");
920
+ }
921
+ write(chunk) {
922
+ if (this.#fd === null) this.#open();
923
+ if (typeof chunk === "string") fs.writeSync(this.#fd, chunk);
924
+ else fs.writeSync(this.#fd, chunk);
925
+ }
926
+ flush() {}
927
+ end() {
928
+ this.close();
929
+ }
930
+ close() {
931
+ if (this.#fd !== null) {
932
+ fs.closeSync(this.#fd);
933
+ this.#fd = null;
934
+ }
935
+ }
936
+ };
937
+ async function bunWrite(dest, data) {
938
+ await fsp.mkdir(path.dirname(dest), { recursive: true });
939
+ if (typeof data === "string") await fsp.writeFile(dest, data);
940
+ else if (data instanceof Blob) await fsp.writeFile(dest, new Uint8Array(await data.arrayBuffer()));
941
+ else if (data instanceof ArrayBuffer) await fsp.writeFile(dest, new Uint8Array(data));
942
+ else await fsp.writeFile(dest, data);
943
+ }
944
+ function bunSleep(ms) {
945
+ return new Promise((resolve) => setTimeout(resolve, ms));
946
+ }
947
+ function bunRandomUUIDv7() {
948
+ return randomUUID();
949
+ }
950
+ const PRIME64_1 = 11400714785074694791n;
951
+ const PRIME64_2 = 14029467366897019727n;
952
+ const PRIME64_3 = 1609587929392839161n;
953
+ const PRIME64_4 = 9650029242287828579n;
954
+ const PRIME64_5 = 2870177450012600261n;
955
+ const MASK64 = 18446744073709551615n;
956
+ function rotl64(x, r) {
957
+ return (x << BigInt(r) | x >> BigInt(64 - r)) & MASK64;
958
+ }
959
+ function xx64Round(acc, input) {
960
+ acc = acc + input * PRIME64_2 & MASK64;
961
+ acc = rotl64(acc, 31);
962
+ return acc * PRIME64_1 & MASK64;
963
+ }
964
+ function xx64MergeRound(acc, val) {
965
+ val = xx64Round(0n, val);
966
+ return (acc ^ val) * PRIME64_1 + PRIME64_4 & MASK64;
967
+ }
968
+ function xx64ReadU64(bytes, o) {
969
+ return BigInt(bytes[o]) | BigInt(bytes[o + 1]) << 8n | BigInt(bytes[o + 2]) << 16n | BigInt(bytes[o + 3]) << 24n | BigInt(bytes[o + 4]) << 32n | BigInt(bytes[o + 5]) << 40n | BigInt(bytes[o + 6]) << 48n | BigInt(bytes[o + 7]) << 56n;
970
+ }
971
+ function bunHashXxHash64(data, seed) {
972
+ let bytes;
973
+ if (typeof data === "string") bytes = new TextEncoder().encode(data);
974
+ else if (data instanceof ArrayBuffer) bytes = new Uint8Array(data);
975
+ else bytes = data;
976
+ return xxHash64Core(bytes, seed);
977
+ }
978
+ function xxHash64Core(bytes, seed) {
979
+ const s = seed ?? 0n;
980
+ const len = bytes.length;
981
+ let i = 0;
982
+ let h;
983
+ if (len >= 32) {
984
+ let v1 = s + PRIME64_1 + PRIME64_2 & MASK64;
985
+ let v2 = s + PRIME64_2 & MASK64;
986
+ let v3 = s + 0n & MASK64;
987
+ let v4 = s - PRIME64_1 & MASK64;
988
+ for (; i <= len - 32; i += 32) {
989
+ v1 = xx64Round(v1, xx64ReadU64(bytes, i));
990
+ v2 = xx64Round(v2, xx64ReadU64(bytes, i + 8));
991
+ v3 = xx64Round(v3, xx64ReadU64(bytes, i + 16));
992
+ v4 = xx64Round(v4, xx64ReadU64(bytes, i + 24));
993
+ }
994
+ h = rotl64(v1, 1) + rotl64(v2, 7) + rotl64(v3, 12) + rotl64(v4, 18) & MASK64;
995
+ h = xx64MergeRound(h, xx64ReadU64(bytes, i));
996
+ i += 8;
997
+ h = xx64MergeRound(h, xx64ReadU64(bytes, i));
998
+ i += 8;
999
+ h = xx64MergeRound(h, xx64ReadU64(bytes, i));
1000
+ i += 8;
1001
+ h = xx64MergeRound(h, xx64ReadU64(bytes, i));
1002
+ i += 8;
1003
+ } else h = s + PRIME64_5 & MASK64;
1004
+ h = h + BigInt(len) & MASK64;
1005
+ for (; i + 8 <= len; i += 8) {
1006
+ h = xx64MergeRound(h, xx64ReadU64(bytes, i));
1007
+ h = rotl64(h, 27) * PRIME64_1 + PRIME64_4 & MASK64;
1008
+ }
1009
+ if (i + 4 <= len) {
1010
+ const v = BigInt(bytes[i]) | BigInt(bytes[i + 1]) << 8n | BigInt(bytes[i + 2]) << 16n | BigInt(bytes[i + 3]) << 24n;
1011
+ h = (h ^ v * PRIME64_1) & MASK64;
1012
+ h = rotl64(h, 23) * PRIME64_2 + PRIME64_3 & MASK64;
1013
+ i += 4;
1014
+ }
1015
+ for (; i < len; i++) {
1016
+ h = (h ^ BigInt(bytes[i]) * PRIME64_5) & MASK64;
1017
+ h = rotl64(h, 11) * PRIME64_1 & MASK64;
1018
+ }
1019
+ h ^= h >> 33n;
1020
+ h = h * PRIME64_2 & MASK64;
1021
+ h ^= h >> 29n;
1022
+ h = h * PRIME64_3 & MASK64;
1023
+ h ^= h >> 32n;
1024
+ return h & MASK64;
1025
+ }
1026
+ function bunHash(data) {
1027
+ let bytes;
1028
+ if (typeof data === "string") bytes = new TextEncoder().encode(data);
1029
+ else if (data instanceof ArrayBuffer) bytes = new Uint8Array(data);
1030
+ else bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
1031
+ let h = 2166136261;
1032
+ for (const b of bytes) {
1033
+ h ^= b;
1034
+ h = Math.imul(h, 16777619) >>> 0;
1035
+ }
1036
+ return h >>> 0;
1037
+ }
1038
+ function isWideChar(c) {
1039
+ return c >= 4352 && (c <= 4447 || c === 9001 || c === 9002 || c >= 11904 && c <= 42191 && c !== 12351 || c >= 44032 && c <= 55203 || c >= 63744 && c <= 64255 || c >= 65040 && c <= 65049 || c >= 65072 && c <= 65135 || c >= 65280 && c <= 65376 || c >= 65504 && c <= 65510 || c >= 127744 && c <= 129791 || c >= 131072 && c <= 262141);
1040
+ }
1041
+ function bunStringWidth(s) {
1042
+ let width = 0;
1043
+ for (const ch of s) {
1044
+ const cp = ch.codePointAt(0);
1045
+ width += isWideChar(cp) ? 2 : 1;
1046
+ }
1047
+ return width;
1048
+ }
1049
+ var GlobShim = class {
1050
+ #re;
1051
+ constructor(pattern) {
1052
+ let src = "";
1053
+ for (let i = 0; i < pattern.length; i++) {
1054
+ const c = pattern[i];
1055
+ if (c === "*") {
1056
+ if (pattern[i + 1] === "*") {
1057
+ src += ".*";
1058
+ i++;
1059
+ } else src += "[^/]*";
1060
+ } else if (c === "?") src += "[^/]";
1061
+ else src += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
1062
+ }
1063
+ this.#re = new RegExp(`^${src}$`);
1064
+ }
1065
+ async *scan(opts = {}) {
1066
+ const root = opts.cwd ?? process.cwd();
1067
+ const onlyFiles = opts.onlyFiles ?? true;
1068
+ for await (const abs of walkFiles(root, onlyFiles)) {
1069
+ const rel = path.relative(root, abs).split(path.sep).join("/");
1070
+ if (this.#re.test(rel)) yield opts.absolute ? abs : rel;
1071
+ }
1072
+ }
1073
+ match(input) {
1074
+ return this.#re.test(input);
1075
+ }
1076
+ };
1077
+ async function* walkFiles(dir, onlyFiles) {
1078
+ let entries;
1079
+ try {
1080
+ entries = await fsp.readdir(dir, { withFileTypes: true });
1081
+ } catch {
1082
+ return;
1083
+ }
1084
+ for (const entry of entries) {
1085
+ const p = path.join(dir, entry.name);
1086
+ if (entry.isDirectory()) {
1087
+ if (!onlyFiles) yield p;
1088
+ yield* walkFiles(p, onlyFiles);
1089
+ } else if (entry.isFile() || entry.isSymbolicLink()) yield p;
1090
+ }
1091
+ }
1092
+ var CryptoHasherShim = class {
1093
+ #hash;
1094
+ constructor(algorithm) {
1095
+ this.#hash = createHash(algorithm);
1096
+ }
1097
+ update(data) {
1098
+ this.#hash.update(data);
1099
+ return this;
1100
+ }
1101
+ digest(encoding) {
1102
+ if (encoding === "buffer") return this.#hash.digest();
1103
+ return this.#hash.digest(encoding);
1104
+ }
1105
+ };
1106
+ function bunColor(color, format) {
1107
+ const hex = /^#?([0-9a-f]{6})$/i.exec(color);
1108
+ if (!hex) return "";
1109
+ const r = parseInt(hex[1].slice(0, 2), 16);
1110
+ const g = parseInt(hex[1].slice(2, 4), 16);
1111
+ const b = parseInt(hex[1].slice(4, 6), 16);
1112
+ if (format === "ansi16") return `\x1b[${(r + g + b) / 3 >= 128 ? 97 : 30}m`;
1113
+ if (format === "ansi256") return `\x1b[38;5;${16 + 36 * Math.round(r / 255 * 5) + 6 * Math.round(g / 255 * 5) + Math.round(b / 255 * 5)}m`;
1114
+ return `\x1b[38;2;${r};${g};${b}m`;
1115
+ }
1116
+ let sharpLoaderPromise;
1117
+ async function loadSharp() {
1118
+ if (!sharpLoaderPromise) sharpLoaderPromise = resolveSharp();
1119
+ try {
1120
+ return await sharpLoaderPromise;
1121
+ } catch (error) {
1122
+ sharpLoaderPromise = void 0;
1123
+ throw error;
1124
+ }
1125
+ }
1126
+ function normalizeSharp(mod) {
1127
+ const candidate = mod?.default ?? mod;
1128
+ const fn = candidate?.sharp ?? candidate;
1129
+ return typeof fn === "function" ? fn : void 0;
1130
+ }
1131
+ async function resolveSharp() {
1132
+ const errors = [];
1133
+ try {
1134
+ const sharp = normalizeSharp(await import("sharp"));
1135
+ if (sharp) return sharp;
1136
+ errors.push("import(\"sharp\"): no callable export");
1137
+ } catch (error) {
1138
+ errors.push(`import("sharp"): ${error.message.split("\n")[0]}`);
1139
+ }
1140
+ try {
1141
+ const { createRequire } = await import("node:module");
1142
+ const required = normalizeSharp(createRequire(import.meta.url)("sharp"));
1143
+ if (required) return required;
1144
+ errors.push("createRequire(\"sharp\"): no callable export");
1145
+ } catch (error) {
1146
+ errors.push(`createRequire("sharp"): ${error.message.split("\n")[0]}`);
1147
+ }
1148
+ try {
1149
+ const { createRequire } = await import("node:module");
1150
+ const { fileURLToPath } = await import("node:url");
1151
+ const { existsSync } = await import("node:fs");
1152
+ let dir = path.dirname(fileURLToPath(import.meta.url));
1153
+ for (let depth = 0; depth < 10; depth++) {
1154
+ const parent = path.dirname(dir);
1155
+ if (parent === dir) break;
1156
+ if (existsSync(path.join(parent, "node_modules", "@deepseek-ai", "dsh", "node_modules", "sharp", "package.json"))) {
1157
+ const sharp = normalizeSharp(createRequire(path.join(parent, "noop.js"))("sharp"));
1158
+ if (sharp) return sharp;
1159
+ }
1160
+ dir = parent;
1161
+ }
1162
+ } catch {}
1163
+ throw new Error(`DSH: Bun.Image 需要 sharp,但运行时解析失败(${errors.join(";")})。sharp 应随宿主 @deepseek-ai/dsh(dsh-attachment-local)安装;图片缩放将按调用方的降级路径处理。`);
1164
+ }
1165
+ /** Map sharp's format vocabulary onto Bun.Image's (width/height/format[/hasAlpha]). */
1166
+ function mapImageMetadata(meta) {
1167
+ const format = typeof meta.format === "string" ? meta.format : void 0;
1168
+ return {
1169
+ width: typeof meta.width === "number" ? meta.width : 0,
1170
+ height: typeof meta.height === "number" ? meta.height : 0,
1171
+ ...format !== void 0 ? { format } : {},
1172
+ ...typeof meta.channels === "number" ? { hasAlpha: meta.channels === 2 || meta.channels === 4 } : {}
1173
+ };
1174
+ }
1175
+ var BunImageShim = class BunImageShim {
1176
+ #source;
1177
+ #steps;
1178
+ constructor(bytes, steps) {
1179
+ this.#source = bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes;
1180
+ this.#steps = steps ?? [];
1181
+ }
1182
+ #extend(step) {
1183
+ return new BunImageShim(this.#source, [...this.#steps, step]);
1184
+ }
1185
+ async metadata() {
1186
+ return mapImageMetadata(await (await loadSharp())(this.#source, { failOn: "error" }).metadata());
1187
+ }
1188
+ resize(widthOrOpts, height) {
1189
+ if (typeof widthOrOpts === "object" && widthOrOpts !== null) return this.#extend({
1190
+ op: "resize",
1191
+ args: [widthOrOpts.width, widthOrOpts.height]
1192
+ });
1193
+ return this.#extend({
1194
+ op: "resize",
1195
+ args: [widthOrOpts, height]
1196
+ });
1197
+ }
1198
+ /** Bun's resize() accepts { width, height, fit } — expose fit through to sharp. */
1199
+ resizeWithFit(width, height, fit) {
1200
+ return this.#extend({
1201
+ op: "resize",
1202
+ args: [
1203
+ width,
1204
+ height,
1205
+ { fit }
1206
+ ]
1207
+ });
1208
+ }
1209
+ png() {
1210
+ return this.#extend({
1211
+ op: "png",
1212
+ args: []
1213
+ });
1214
+ }
1215
+ jpeg(opts) {
1216
+ return this.#extend({
1217
+ op: "jpeg",
1218
+ args: [opts]
1219
+ });
1220
+ }
1221
+ webp(opts) {
1222
+ return this.#extend({
1223
+ op: "webp",
1224
+ args: [opts]
1225
+ });
1226
+ }
1227
+ rotate() {
1228
+ return this.#extend({
1229
+ op: "rotate",
1230
+ args: []
1231
+ });
1232
+ }
1233
+ flip(flip) {
1234
+ return this.#extend({
1235
+ op: "flip",
1236
+ args: [flip]
1237
+ });
1238
+ }
1239
+ blur(radius) {
1240
+ return this.#extend({
1241
+ op: "blur",
1242
+ args: [radius]
1243
+ });
1244
+ }
1245
+ sharpen(radius) {
1246
+ return this.#extend({
1247
+ op: "sharpen",
1248
+ args: [radius]
1249
+ });
1250
+ }
1251
+ #build() {
1252
+ return loadSharp().then((sharp) => {
1253
+ let pipe = sharp(this.#source, { failOn: "error" });
1254
+ for (const step of this.#steps) switch (step.op) {
1255
+ case "resize": {
1256
+ const [w, h, extra] = step.args;
1257
+ pipe = pipe.resize(w ?? 0, h ?? 0, { fit: extra?.fit ?? "cover" });
1258
+ break;
1259
+ }
1260
+ case "png":
1261
+ pipe = pipe.png();
1262
+ break;
1263
+ case "jpeg":
1264
+ pipe = pipe.jpeg(step.args[0] ?? void 0);
1265
+ break;
1266
+ case "webp": pipe = pipe.webp(step.args[0] ?? void 0);
1267
+ }
1268
+ return pipe.toBuffer();
1269
+ });
1270
+ }
1271
+ async bytes() {
1272
+ return this.#build();
1273
+ }
1274
+ async toBase64() {
1275
+ return Buffer.from(await this.#build()).toString("base64");
1276
+ }
1277
+ async write(destPath) {
1278
+ const data = await this.#build();
1279
+ await fsp.writeFile(destPath, data);
1280
+ return data.byteLength;
1281
+ }
1282
+ };
1283
+ async function memberToBytes(content) {
1284
+ if (typeof content === "string") return new TextEncoder().encode(content);
1285
+ if (content instanceof Blob) return new Uint8Array(await content.arrayBuffer());
1286
+ return content;
1287
+ }
1288
+ function encodeTarHeader(name, size) {
1289
+ const buf = /* @__PURE__ */ new Uint8Array(512);
1290
+ const enc = new TextEncoder();
1291
+ const put = (offset, len, value) => {
1292
+ buf.set(enc.encode(value).subarray(0, len), offset);
1293
+ };
1294
+ put(0, 100, name);
1295
+ put(100, 8, "0000644\0");
1296
+ put(108, 8, "0000000\0");
1297
+ put(116, 8, "0000000\0");
1298
+ put(124, 12, size.toString(8).padStart(11, "0") + "\0");
1299
+ put(136, 12, "00000000000\0");
1300
+ put(148, 8, " ");
1301
+ put(156, 1, "0");
1302
+ put(257, 6, "ustar\0");
1303
+ put(263, 2, "00");
1304
+ let sum = 0;
1305
+ for (const b of buf) sum += b;
1306
+ const checksum = sum.toString(8).padStart(6, "0");
1307
+ buf.set(enc.encode(checksum + "\0 "), 148);
1308
+ return buf;
1309
+ }
1310
+ async function archiveWrite(destPath, record, opts) {
1311
+ const chunks = [];
1312
+ for (const [name, content] of Object.entries(record)) {
1313
+ const data = await memberToBytes(content);
1314
+ chunks.push(encodeTarHeader(name.replace(/\\/g, "/"), data.length), data);
1315
+ const pad = data.length % 512;
1316
+ if (pad !== 0) chunks.push(new Uint8Array(512 - pad));
1317
+ }
1318
+ chunks.push(/* @__PURE__ */ new Uint8Array(1024));
1319
+ let buf = Buffer.concat(chunks.map((c) => Buffer.from(c.buffer, c.byteOffset, c.byteLength)));
1320
+ if (opts?.compress === "gzip") buf = gzipSync(buf);
1321
+ await fsp.mkdir(path.dirname(destPath), { recursive: true });
1322
+ await fsp.writeFile(destPath, buf);
1323
+ }
1324
+ const ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07]*(?:\x07|\x1b\\)|[()][0-9A-B]|[^\[\]()])/g;
1325
+ function bunStripANSI(text) {
1326
+ return text.replace(ANSI_RE, "");
1327
+ }
1328
+ function installBunShim() {
1329
+ patchBase64();
1330
+ const g = globalThis;
1331
+ if (g.Bun !== void 0) return;
1332
+ g.Bun = {
1333
+ file: (p) => new BunFileShim(p),
1334
+ write: bunWrite,
1335
+ env: process.env,
1336
+ sleep: bunSleep,
1337
+ hash: bunHash,
1338
+ randomUUIDv7: bunRandomUUIDv7,
1339
+ stringWidth: bunStringWidth,
1340
+ Glob: GlobShim,
1341
+ Archive: { write: archiveWrite },
1342
+ FileSink: FileSinkShim,
1343
+ Image: BunImageShim,
1344
+ CryptoHasher: CryptoHasherShim,
1345
+ color: bunColor,
1346
+ stripANSI: bunStripANSI,
1347
+ JSON,
1348
+ JSON5,
1349
+ Encoding: {},
1350
+ native: void 0,
1351
+ BunFile: BunFileShim,
1352
+ version: "node-shim"
1353
+ };
1354
+ const bun = g.Bun;
1355
+ bun.hash.xxHash64 = bunHashXxHash64;
1356
+ }
1357
+ //#endregion
1358
+ //#region src/tools/shared/bun-named-shim.ts
1359
+ /**
1360
+ * DSH named-import shim for `import { ... } from "bun"` (pi-utils and other
1361
+ * always-bundled OMP packages). tsdown aliases "bun" to this module.
1362
+ * Runtime Bun global is installed separately by src/tools/shared/bun-shim.ts.
1363
+ */
1364
+ /** Bun.YAML (pi-utils frontmatter) — yaml package under the hood. */
1365
+ const YAML = {
1366
+ parse: (s) => parse(s),
1367
+ stringify: (v) => stringify(v)
1368
+ };
1369
+ /** Minimal Bun-shell result (pi-ai perplexity OAuth; never reached on non-macOS). */
1370
+ var BunShellResult = class {
1371
+ get exitCode() {
1372
+ return 1;
1373
+ }
1374
+ text() {
1375
+ return "";
1376
+ }
1377
+ quiet() {
1378
+ return this;
1379
+ }
1380
+ nothrow() {
1381
+ return this;
1382
+ }
1383
+ cwd() {
1384
+ return this;
1385
+ }
1386
+ };
1387
+ /** Bun shell template tag (Bun `$`). Perplexity's macOS defaults read only. */
1388
+ function $(_strings, ..._expr) {
1389
+ return new BunShellResult();
1390
+ }
1391
+ /** HTTP cookie (pi-ai OAuth flows). */
1392
+ var Cookie = class Cookie {
1393
+ name;
1394
+ value;
1395
+ #expires;
1396
+ constructor(name, value, expires) {
1397
+ this.name = name;
1398
+ this.value = value;
1399
+ this.#expires = expires;
1400
+ }
1401
+ static parse(header) {
1402
+ const segments = header.split(";");
1403
+ const first = segments[0] ?? "";
1404
+ const eq = first.indexOf("=");
1405
+ const name = eq >= 0 ? first.slice(0, eq).trim() : first.trim();
1406
+ const value = eq >= 0 ? first.slice(eq + 1).trim() : "";
1407
+ let expires;
1408
+ for (const segment of segments.slice(1)) {
1409
+ const [key, val] = segment.trim().split("=");
1410
+ if (key?.toLowerCase() === "expires") {
1411
+ const t = Date.parse(val ?? "");
1412
+ if (!Number.isNaN(t)) expires = new Date(t);
1413
+ }
1414
+ }
1415
+ return new Cookie(name, value, expires);
1416
+ }
1417
+ isExpired() {
1418
+ return this.#expires !== void 0 && this.#expires.getTime() < Date.now();
1419
+ }
1420
+ };
1421
+ /** Ordered cookie jar (Bun CookieMap surface used by pi-ai OAuth). */
1422
+ var CookieMap = class {
1423
+ #map = /* @__PURE__ */ new Map();
1424
+ get size() {
1425
+ return this.#map.size;
1426
+ }
1427
+ set(name, value) {
1428
+ this.#map.set(name, value);
1429
+ }
1430
+ get(name) {
1431
+ return this.#map.get(name);
1432
+ }
1433
+ delete(name) {
1434
+ return this.#map.delete(name);
1435
+ }
1436
+ has(name) {
1437
+ return this.#map.has(name);
1438
+ }
1439
+ clear() {
1440
+ this.#map.clear();
1441
+ }
1442
+ *[Symbol.iterator]() {
1443
+ yield* this.#map;
1444
+ }
1445
+ };
1446
+ //#endregion
1447
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/logger/rotating-file.ts
1448
+ /** Behavior-compatible reimplementation of winston-daily-rotate-file's used surface. */
1449
+ function isAuditEntry(value) {
1450
+ if (value === null || typeof value !== "object") return false;
1451
+ const entry = value;
1452
+ return typeof entry.date === "number" && typeof entry.name === "string" && typeof entry.hash === "string";
1453
+ }
1454
+ /** Synchronous append sink with local-day and size rotation plus bounded retention. */
1455
+ var RotatingFileSink = class {
1456
+ #directory;
1457
+ #filenamePrefix;
1458
+ #filenameSuffix;
1459
+ #auditFile;
1460
+ #maxBytes;
1461
+ #maxFiles;
1462
+ #files;
1463
+ #activeDay;
1464
+ #activeIndex = 0;
1465
+ #activePath;
1466
+ #activeBytes = 0;
1467
+ #closed = false;
1468
+ constructor(options) {
1469
+ this.#directory = options.directory;
1470
+ this.#filenamePrefix = options.filenamePrefix;
1471
+ this.#filenameSuffix = options.filenameSuffix;
1472
+ this.#auditFile = options.auditFile;
1473
+ this.#maxBytes = options.maxBytes;
1474
+ this.#maxFiles = options.maxFiles;
1475
+ this.#files = this.#readAudit();
1476
+ const now = /* @__PURE__ */ new Date();
1477
+ this.#selectFile(this.#localDay(now));
1478
+ const activePath = this.#activePath;
1479
+ if (activePath) {
1480
+ this.#registerFile(activePath, now.getTime());
1481
+ fs.closeSync(fs.openSync(activePath, "a"));
1482
+ }
1483
+ }
1484
+ /** Append one already-formatted log record. */
1485
+ write(line) {
1486
+ if (this.#closed) return;
1487
+ const now = /* @__PURE__ */ new Date();
1488
+ this.#selectFile(this.#localDay(now));
1489
+ const activePath = this.#activePath;
1490
+ if (!activePath) return;
1491
+ this.#registerFile(activePath, now.getTime());
1492
+ const record = `${line}${os.EOL}`;
1493
+ fs.appendFileSync(activePath, record, "utf8");
1494
+ this.#activeBytes += Buffer.byteLength(record);
1495
+ }
1496
+ /** Stop accepting records. Synchronous writes require no drain phase. */
1497
+ close() {
1498
+ this.#closed = true;
1499
+ }
1500
+ #localDay(date) {
1501
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
1502
+ }
1503
+ #selectFile(day) {
1504
+ if (day !== this.#activeDay) {
1505
+ this.#activeDay = day;
1506
+ this.#activeIndex = 0;
1507
+ this.#setActivePath(day, 0);
1508
+ }
1509
+ while (this.#activeBytes > this.#maxBytes) {
1510
+ this.#activeIndex++;
1511
+ this.#setActivePath(day, this.#activeIndex);
1512
+ }
1513
+ }
1514
+ #setActivePath(day, index) {
1515
+ const suffix = index === 0 ? "" : `.${index}`;
1516
+ this.#activePath = path.join(this.#directory, `${this.#filenamePrefix}.${day}.${this.#filenameSuffix}.log${suffix}`);
1517
+ try {
1518
+ this.#activeBytes = fs.statSync(this.#activePath).size;
1519
+ } catch {
1520
+ this.#activeBytes = 0;
1521
+ }
1522
+ }
1523
+ #registerFile(filePath, date) {
1524
+ if (this.#files.some((file) => file.name === filePath)) return;
1525
+ const hash = crypto$1.createHash("sha256").update(`${filePath}LOG_FILE${date}`).digest("hex");
1526
+ this.#files.push({
1527
+ date,
1528
+ name: filePath,
1529
+ hash
1530
+ });
1531
+ while (this.#files.length > this.#maxFiles) {
1532
+ const removed = this.#files.shift();
1533
+ if (!removed) break;
1534
+ try {
1535
+ fs.rmSync(removed.name, { force: true });
1536
+ } catch {}
1537
+ }
1538
+ this.#writeAudit();
1539
+ }
1540
+ #readAudit() {
1541
+ try {
1542
+ const parsed = JSON.parse(fs.readFileSync(this.#auditFile, "utf8"));
1543
+ return Array.isArray(parsed.files) ? parsed.files.filter(isAuditEntry) : [];
1544
+ } catch {
1545
+ return [];
1546
+ }
1547
+ }
1548
+ #writeAudit() {
1549
+ const state = {
1550
+ keep: {
1551
+ days: false,
1552
+ amount: this.#maxFiles
1553
+ },
1554
+ auditLog: this.#auditFile,
1555
+ files: this.#files,
1556
+ hashType: "sha256"
1557
+ };
1558
+ fs.writeFileSync(this.#auditFile, JSON.stringify(state, void 0, 4), "utf8");
1559
+ }
1560
+ };
1561
+ //#endregion
1562
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/logger.ts
1563
+ /**
1564
+ * Centralized logger for omp.
1565
+ *
1566
+ * Default: rotating `~/.omp/logs/omp.<DATE>.<PID>.log`, no console output (writing
1567
+ * to stdout/stderr would corrupt the TUI). Long-running headless services
1568
+ * (the auth broker, etc.) call {@link setTransports} to swap in a console
1569
+ * transport so a process supervisor (pm2, journald, k8s) captures the logs.
1570
+ *
1571
+ * Each entry includes `process.pid` so concurrent omp instances stay
1572
+ * traceable.
1573
+ */
1574
+ const logSinks = /* @__PURE__ */ new Set();
1575
+ function emitToSinks(level, message, context) {
1576
+ if (logSinks.size === 0) return;
1577
+ const event = {
1578
+ level,
1579
+ message,
1580
+ context,
1581
+ timestamp: /* @__PURE__ */ new Date()
1582
+ };
1583
+ for (const sink of logSinks) try {
1584
+ sink(event);
1585
+ } catch {}
1586
+ }
1587
+ const PROCESS_LOG_PATTERN = /^omp\.(\d{4}-\d{2}-\d{2})\.(\d+)\.log(?:\.(\d+))?$/;
1588
+ const PROCESS_AUDIT_PATTERN = /^\.omp\.(\d+)-audit\.json$/;
1589
+ const RETAINED_STALE_LOGS_PER_PROCESS_DAY = 1;
1590
+ function processIsRunning(pid) {
1591
+ try {
1592
+ process.kill(pid, 0);
1593
+ return true;
1594
+ } catch (error) {
1595
+ return !(error instanceof Error && "code" in error && (error.code === "ESRCH" || error.code === "EINVAL"));
1596
+ }
1597
+ }
1598
+ /**
1599
+ * Retain one newest completed-process log per process/day within the current
1600
+ * and previous four local calendar days, and remove one-use audit files. Live
1601
+ * PID namespaces are never touched. The calendar-day boundary preserves daily
1602
+ * diagnostic coverage while bounding completed-process storage and scans.
1603
+ */
1604
+ function pruneStaleProcessLogs(dir) {
1605
+ let entries;
1606
+ try {
1607
+ entries = fs.readdirSync(dir, { withFileTypes: true });
1608
+ } catch {
1609
+ return;
1610
+ }
1611
+ const current = /* @__PURE__ */ new Date();
1612
+ const currentDate = `${current.getFullYear()}-${String(current.getMonth() + 1).padStart(2, "0")}-` + String(current.getDate()).padStart(2, "0");
1613
+ const cutoff = new Date(current);
1614
+ cutoff.setDate(cutoff.getDate() - 4);
1615
+ const cutoffDate = `${cutoff.getFullYear()}-${String(cutoff.getMonth() + 1).padStart(2, "0")}-` + String(cutoff.getDate()).padStart(2, "0");
1616
+ const staleLogsByProcessDay = /* @__PURE__ */ new Map();
1617
+ for (const entry of entries) {
1618
+ if (!entry.isFile()) continue;
1619
+ const logMatch = PROCESS_LOG_PATTERN.exec(entry.name);
1620
+ const auditMatch = PROCESS_AUDIT_PATTERN.exec(entry.name);
1621
+ const pidText = logMatch?.[2] ?? auditMatch?.[1];
1622
+ if (!pidText || processIsRunning(Number(pidText))) continue;
1623
+ const entryPath = path.join(dir, entry.name);
1624
+ if (auditMatch) {
1625
+ try {
1626
+ fs.rmSync(entryPath, { force: true });
1627
+ } catch {}
1628
+ continue;
1629
+ }
1630
+ if (!logMatch?.[1]) continue;
1631
+ if (logMatch[1] < cutoffDate || logMatch[1] > currentDate) {
1632
+ try {
1633
+ fs.rmSync(entryPath, { force: true });
1634
+ } catch {}
1635
+ continue;
1636
+ }
1637
+ try {
1638
+ const key = `${pidText}:${logMatch[1]}`;
1639
+ const staleLogs = staleLogsByProcessDay.get(key) ?? [];
1640
+ staleLogs.push({
1641
+ path: entryPath,
1642
+ mtimeMs: fs.statSync(entryPath).mtimeMs,
1643
+ rollover: Number(logMatch[3] ?? 0)
1644
+ });
1645
+ staleLogsByProcessDay.set(key, staleLogs);
1646
+ } catch {}
1647
+ }
1648
+ for (const staleLogs of staleLogsByProcessDay.values()) {
1649
+ staleLogs.sort((a, b) => b.mtimeMs - a.mtimeMs || b.rollover - a.rollover || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
1650
+ for (const stale of staleLogs.slice(RETAINED_STALE_LOGS_PER_PROCESS_DAY)) try {
1651
+ fs.rmSync(stale.path, { force: true });
1652
+ } catch {}
1653
+ }
1654
+ }
1655
+ /** Ensure a logs directory exists; return the resolved path. */
1656
+ function ensureDir(dir) {
1657
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
1658
+ return dir;
1659
+ }
1660
+ /**
1661
+ * JSON.stringify replacer that unwraps {@link Error} instances. Error's own
1662
+ * properties are non-enumerable, so a plain `JSON.stringify(err)` produces
1663
+ * `"{}"`. Without this, a context like `{ err }` lost every useful field and
1664
+ * forensic logs showed only an opaque empty object.
1665
+ */
1666
+ function jsonReplacer(_key, value) {
1667
+ if (value instanceof Error) {
1668
+ const out = {
1669
+ name: value.name,
1670
+ message: value.message,
1671
+ stack: value.stack
1672
+ };
1673
+ const errAsRecord = value;
1674
+ for (const k in errAsRecord) out[k] = errAsRecord[k];
1675
+ if (value.cause !== void 0) out.cause = value.cause;
1676
+ return out;
1677
+ }
1678
+ return value;
1679
+ }
1680
+ function padTimestampPart(value, width = 2) {
1681
+ return String(value).padStart(width, "0");
1682
+ }
1683
+ function formatLocalTimestamp(date) {
1684
+ const offsetMinutes = -date.getTimezoneOffset();
1685
+ const absoluteOffset = Math.abs(offsetMinutes);
1686
+ const offsetSign = offsetMinutes >= 0 ? "+" : "-";
1687
+ return `${padTimestampPart(date.getFullYear(), 4)}-${padTimestampPart(date.getMonth() + 1)}-${padTimestampPart(date.getDate())}T${padTimestampPart(date.getHours())}:${padTimestampPart(date.getMinutes())}:${padTimestampPart(date.getSeconds())}.${padTimestampPart(date.getMilliseconds(), 3)}${offsetSign}${padTimestampPart(Math.floor(absoluteOffset / 60))}:${padTimestampPart(absoluteOffset % 60)}`;
1688
+ }
1689
+ const FORMAT_TOKEN_PATTERN = /%[scdjifoO%]/;
1690
+ function normalizeLogInfo(level, message, context) {
1691
+ const metadata = !FORMAT_TOKEN_PATTERN.test(message) && context !== null && typeof context === "object" ? context : void 0;
1692
+ const info = Object.assign({}, metadata, {
1693
+ level,
1694
+ message
1695
+ });
1696
+ if (metadata?.message) info.message = `${message} ${metadata.message}`;
1697
+ if (metadata?.stack) info.stack = metadata.stack;
1698
+ if (metadata?.cause) info.cause = metadata.cause;
1699
+ return info;
1700
+ }
1701
+ function formatLogInfo(info) {
1702
+ const timestamp = formatLocalTimestamp(/* @__PURE__ */ new Date());
1703
+ info.timestamp = timestamp;
1704
+ const entry = {
1705
+ timestamp,
1706
+ level: info.level,
1707
+ pid: process.pid,
1708
+ message: info.message
1709
+ };
1710
+ for (const [key, value] of Object.entries(info)) if (key !== "level" && key !== "timestamp" && key !== "message") entry[key] = value;
1711
+ return JSON.stringify(entry, jsonReplacer);
1712
+ }
1713
+ /** Build a rotating file sink with process-local rotation and shared retention. */
1714
+ function makeFileTransport(dir) {
1715
+ const logsDir = ensureDir(dir ?? getLogsDir());
1716
+ pruneStaleProcessLogs(logsDir);
1717
+ return new RotatingFileSink({
1718
+ directory: logsDir,
1719
+ filenamePrefix: "omp",
1720
+ filenameSuffix: String(process.pid),
1721
+ maxBytes: 10485760,
1722
+ maxFiles: 5,
1723
+ auditFile: path.join(logsDir, `.omp.${process.pid}-audit.json`)
1724
+ });
1725
+ }
1726
+ /**
1727
+ * Desired transport configuration, applied when local logging is initialized.
1728
+ * Default: file ON (TUI-safe), console OFF.
1729
+ */
1730
+ let transportOpts = { file: true };
1731
+ /** Local transports, constructed lazily on first log emission. */
1732
+ let activeTransports;
1733
+ function buildTransports(opts) {
1734
+ return {
1735
+ file: opts.file ? makeFileTransport(typeof opts.file === "string" ? opts.file : void 0) : void 0,
1736
+ console: opts.console === true
1737
+ };
1738
+ }
1739
+ function getLocalTransports() {
1740
+ activeTransports ??= buildTransports(transportOpts);
1741
+ return activeTransports;
1742
+ }
1743
+ function emitLocally(level, message, context) {
1744
+ const transports = getLocalTransports();
1745
+ const info = normalizeLogInfo(level, message, context);
1746
+ if (!transports.file && !transports.console) return;
1747
+ const line = formatLogInfo(info);
1748
+ if (transports.file) transports.file.write(line);
1749
+ if (transports.console) fs.writeSync(1, `${formatLogInfo(info)}${os.EOL}`);
1750
+ }
1751
+ /**
1752
+ * Log an error message.
1753
+ * @param message - The message to log.
1754
+ * @param context - The context to log.
1755
+ */
1756
+ function error(message, context) {
1757
+ try {
1758
+ emitLocally("error", message, context);
1759
+ } catch {}
1760
+ emitToSinks("error", message, context);
1761
+ }
1762
+ /**
1763
+ * Log a warning message.
1764
+ * @param message - The message to log.
1765
+ * @param context - The context to log.
1766
+ */
1767
+ function warn(message, context) {
1768
+ try {
1769
+ emitLocally("warn", message, context);
1770
+ } catch {}
1771
+ emitToSinks("warn", message, context);
1772
+ }
1773
+ /**
1774
+ * Log a debug message.
1775
+ * @param message - The message to log.
1776
+ * @param context - The context to log.
1777
+ */
1778
+ function debug(message, context) {
1779
+ try {
1780
+ emitLocally("debug", message, context);
1781
+ } catch {}
1782
+ emitToSinks("debug", message, context);
1783
+ }
1784
+ /**
1785
+ * Streaming startup markers, enabled by `PI_DEBUG_STARTUP`. Unlike the
1786
+ * PI_TIMING tree (printed only after startup completes), these write one
1787
+ * synchronous stderr line as each phase begins/ends, so a hard hang still
1788
+ * shows the last phase that started. `fs.writeSync(2)` is used deliberately:
1789
+ * it cannot be reordered or buffered past a synchronous block of the event
1790
+ * loop (dlopen, sync fs on a dead mount, spawnSync).
1791
+ */
1792
+ function startupMarker(text) {
1793
+ if (!process.env.PI_DEBUG_STARTUP) return;
1794
+ try {
1795
+ fs.writeSync(2, `[startup] ${text}\n`);
1796
+ } catch {}
1797
+ }
1798
+ new AsyncLocalStorage();
1799
+ function time(op, fn, ...args) {
1800
+ if (fn === void 0) {
1801
+ startupMarker(op);
1802
+ return;
1803
+ }
1804
+ if (!process.env.PI_DEBUG_STARTUP) return fn(...args);
1805
+ startupMarker(`${op}:start`);
1806
+ const finish = (ok) => {
1807
+ startupMarker(ok ? `${op}:done` : `${op}:fail`);
1808
+ };
1809
+ try {
1810
+ const result = fn(...args);
1811
+ if (isPromise(result)) return result.then((value) => {
1812
+ finish(true);
1813
+ return value;
1814
+ }, (error) => {
1815
+ finish(false);
1816
+ throw error;
1817
+ });
1818
+ finish(true);
1819
+ return result;
1820
+ } catch (error) {
1821
+ finish(false);
1822
+ throw error;
1823
+ }
1824
+ }
1825
+ //#endregion
1826
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/frontmatter.ts
1827
+ function stripHtmlComments(content) {
1828
+ return content.replace(/<!--[\s\S]*?-->/g, "");
1829
+ }
1830
+ /** Convert kebab-case to camelCase (e.g. "thinking-level" -> "thinkingLevel") */
1831
+ function kebabToCamel(key) {
1832
+ if (!key.includes("-")) return key;
1833
+ return key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1834
+ }
1835
+ /**
1836
+ * Recursively normalize object keys from kebab-case to camelCase — the
1837
+ * representation convention for frontmatter consumed inside this codebase.
1838
+ * Exported for loaders that parse with `rawKeys: true` to validate exact
1839
+ * spec-defined keys, then normalize for storage.
1840
+ */
1841
+ function normalizeFrontmatterKeys(obj) {
1842
+ if (obj === null || typeof obj !== "object") return obj;
1843
+ if (Array.isArray(obj)) {
1844
+ let changed = false;
1845
+ const out = new Array(obj.length);
1846
+ for (let i = 0; i < obj.length; i++) {
1847
+ const v = obj[i];
1848
+ const nv = normalizeFrontmatterKeys(v);
1849
+ out[i] = nv;
1850
+ if (nv !== v) changed = true;
1851
+ }
1852
+ return changed ? out : obj;
1853
+ }
1854
+ let changed = false;
1855
+ const result = {};
1856
+ for (const [key, value] of Object.entries(obj)) {
1857
+ const nk = key.includes("-") ? kebabToCamel(key) : key;
1858
+ const nv = normalizeFrontmatterKeys(value);
1859
+ result[nk] = nv;
1860
+ if (nk !== key || nv !== value) changed = true;
1861
+ }
1862
+ return changed ? result : obj;
1863
+ }
1864
+ const PLAIN_SCALAR_KEY_VALUE = /^(\s*[A-Za-z_][\w-]*:\s+)(\S.*?)(\s*)$/;
1865
+ const FLOW_OR_EXPLICIT_VALUE_START = /* @__PURE__ */ new Set([
1866
+ "\"",
1867
+ "'",
1868
+ "[",
1869
+ "{",
1870
+ "|",
1871
+ ">",
1872
+ "!",
1873
+ "&",
1874
+ "*",
1875
+ "#"
1876
+ ]);
1877
+ function quoteAmbiguousPlainScalars(metadata) {
1878
+ let changed = false;
1879
+ const lines = metadata.split("\n").map((line) => {
1880
+ const match = line.match(PLAIN_SCALAR_KEY_VALUE);
1881
+ if (!match) return line;
1882
+ const [, prefix, rawValue, suffix] = match;
1883
+ const value = rawValue.trimEnd();
1884
+ if (!value.includes(": ")) return line;
1885
+ if (FLOW_OR_EXPLICIT_VALUE_START.has(value[0])) return line;
1886
+ changed = true;
1887
+ return `${prefix}${JSON.stringify(value)}${suffix}`;
1888
+ });
1889
+ return changed ? lines.join("\n") : void 0;
1890
+ }
1891
+ function parseYamlRecord(metadata, repairTabs) {
1892
+ const loaded = YAML.parse(repairTabs ? metadata.replaceAll(" ", " ") : metadata);
1893
+ if (loaded === null || loaded === void 0) return null;
1894
+ if (typeof loaded !== "object" || Array.isArray(loaded)) return null;
1895
+ return loaded;
1896
+ }
1897
+ var FrontmatterError = class extends Error {
1898
+ source;
1899
+ constructor(error, source) {
1900
+ super(`Failed to parse YAML frontmatter (${source}): ${error.message}`, { cause: error });
1901
+ this.source = source;
1902
+ this.name = "FrontmatterError";
1903
+ }
1904
+ toString() {
1905
+ const details = [this.message];
1906
+ if (this.source !== void 0) details.push(`Source: ${JSON.stringify(this.source)}`);
1907
+ if (this.cause && typeof this.cause === "object" && "stack" in this.cause && this.cause.stack) details.push(`Stack:\n${this.cause.stack}`);
1908
+ else if (this.stack) details.push(`Stack:\n${this.stack}`);
1909
+ return details.join("\n\n");
1910
+ }
1911
+ };
1912
+ /**
1913
+ * Parse YAML frontmatter from markdown content
1914
+ * Returns { frontmatter, body } where body has frontmatter stripped
1915
+ */
1916
+ function parseFrontmatter(content, options) {
1917
+ const { location, source, fallback, normalize = true, level = "warn", repair = true, rawKeys = false } = options ?? {};
1918
+ const finalizeKeys = (fm) => rawKeys ? fm : normalizeFrontmatterKeys(fm);
1919
+ const loc = location ?? source;
1920
+ const frontmatter = { ...fallback };
1921
+ const newlineNormalized = normalize ? content.replace(/\r\n?/g, "\n") : content;
1922
+ const normalized = normalize && repair ? stripHtmlComments(newlineNormalized) : newlineNormalized;
1923
+ if (!normalized.startsWith("---")) return {
1924
+ frontmatter,
1925
+ body: normalized
1926
+ };
1927
+ const endIndex = normalized.indexOf("\n---", 3);
1928
+ if (endIndex === -1) return {
1929
+ frontmatter,
1930
+ body: normalized
1931
+ };
1932
+ const metadata = normalized.slice(4, endIndex);
1933
+ const body = normalized.slice(endIndex + 4).trim();
1934
+ try {
1935
+ const loaded = parseYamlRecord(metadata, repair);
1936
+ return {
1937
+ frontmatter: finalizeKeys({
1938
+ ...frontmatter,
1939
+ ...loaded
1940
+ }),
1941
+ body
1942
+ };
1943
+ } catch (error) {
1944
+ const quotedMetadata = repair ? quoteAmbiguousPlainScalars(metadata) : void 0;
1945
+ if (quotedMetadata) try {
1946
+ const loaded = parseYamlRecord(quotedMetadata, true);
1947
+ return {
1948
+ frontmatter: finalizeKeys({
1949
+ ...frontmatter,
1950
+ ...loaded
1951
+ }),
1952
+ body
1953
+ };
1954
+ } catch {}
1955
+ const err = new FrontmatterError(error instanceof Error ? error : /* @__PURE__ */ new Error(`YAML: ${error}`), loc ?? `Inline '${truncate(content, 64)}'`);
1956
+ if (level === "warn" || level === "fatal") warn("Failed to parse YAML frontmatter", { err: err.toString() });
1957
+ if (level === "fatal") throw err;
1958
+ for (const line of metadata.split("\n")) {
1959
+ const match = line.match(/^([\w-]+):\s*(.*)$/);
1960
+ if (!match) continue;
1961
+ const raw = match[2].trim();
1962
+ let value = raw;
1963
+ if (raw.length > 0) try {
1964
+ const parsed = YAML.parse(raw);
1965
+ if (parsed !== null && typeof parsed !== "object") value = parsed;
1966
+ else if (Array.isArray(parsed)) value = parsed;
1967
+ } catch {}
1968
+ frontmatter[match[1]] = value;
1969
+ }
1970
+ return {
1971
+ frontmatter: finalizeKeys(frontmatter),
1972
+ body
1973
+ };
1974
+ }
1975
+ }
1976
+ //#endregion
1977
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/fs-error.ts
1978
+ function isFsError(err) {
1979
+ return err instanceof Error && "code" in err && typeof err.code === "string";
1980
+ }
1981
+ function isEnoent(err) {
1982
+ return isFsError(err) && err.code === "ENOENT";
1983
+ }
1984
+ function isEnotdir(err) {
1985
+ return isFsError(err) && err.code === "ENOTDIR";
1986
+ }
1987
+ function hasFsCode(err, code) {
1988
+ return isFsError(err) && err.code === code;
1989
+ }
1990
+ //#endregion
1991
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/json-parse.ts
1992
+ const QUOTE = 34;
1993
+ const BACKSLASH = 92;
1994
+ const U = 117;
1995
+ const SQUOTE = 39;
1996
+ const VALID_ESCAPE_CHAR = /* @__PURE__ */ new Uint8Array(128);
1997
+ for (const ch of "\"\\/bfnrtu") VALID_ESCAPE_CHAR[ch.charCodeAt(0)] = 1;
1998
+ (() => {
1999
+ const e = [];
2000
+ e[8] = "\\b";
2001
+ e[9] = "\\t";
2002
+ e[10] = "\\n";
2003
+ e[12] = "\\f";
2004
+ e[13] = "\\r";
2005
+ for (let cp = 0; cp <= 31; cp++) e[cp] ??= `\\u${cp.toString(16).padStart(4, "0")}`;
2006
+ return e;
2007
+ })();
2008
+ const HEX4_RE = /^[0-9a-fA-F]{4}$/;
2009
+ function isHexDigit(cp) {
2010
+ return cp >= 48 && cp <= 57 || (cp | 32) >= 97 && (cp | 32) <= 102;
2011
+ }
2012
+ function isWhitespace(cp) {
2013
+ return cp === 32 || cp === 9 || cp === 10 || cp === 13;
2014
+ }
2015
+ function isIdentChar(cp) {
2016
+ return cp >= 48 && cp <= 57 || (cp | 32) >= 97 && (cp | 32) <= 122 || cp === 95 || cp === 36;
2017
+ }
2018
+ /** Bareword literals: standard JSON plus Python `True`/`False`/`None`. */
2019
+ const KEYWORDS = [
2020
+ ["true", true],
2021
+ ["false", false],
2022
+ ["null", null],
2023
+ ["True", true],
2024
+ ["False", false],
2025
+ ["None", null]
2026
+ ];
2027
+ /**
2028
+ * JS-only atoms never recovered as bareword strings — a tool must not execute
2029
+ * with a non-finite or undefined argument masquerading as a string.
2030
+ */
2031
+ const NON_RECOVERABLE_BAREWORDS = {
2032
+ NaN: true,
2033
+ Infinity: true,
2034
+ "-Infinity": true,
2035
+ "+Infinity": true,
2036
+ undefined: true
2037
+ };
2038
+ /**
2039
+ * Sentinel returned by partial-mode value parsing when an atomic value
2040
+ * (number / keyword) is incomplete at the streaming edge, so the enclosing
2041
+ * object/array rolls back to the last valid prefix instead of committing junk.
2042
+ */
2043
+ const INCOMPLETE = Symbol("incomplete");
2044
+ /**
2045
+ * Recursive-descent parser for a forgiving superset of JSON. Beyond strict JSON
2046
+ * it accepts, and normalizes, the malformations LLM tool-call bodies leak in
2047
+ * practice:
2048
+ *
2049
+ * - single-quoted strings and unquoted object keys (JSON5);
2050
+ * - trailing / stray commas, and `//` + block comments;
2051
+ * - Python literals `True` / `False` / `None` and JS `NaN` / `Infinity`;
2052
+ * - raw control characters and invalid `\x` escapes inside strings (kept literally);
2053
+ * - unescaped quotes inside strings — a quote only closes a string when followed
2054
+ * by a value terminator, recovering apostrophes such as `'it's'`;
2055
+ * - unquoted string values in object/array value position (strict mode only) —
2056
+ * an unrecognized bareword such as `{"paths": packages/foo/*}` is recovered as
2057
+ * a string up to the next `,` / `}` / `]` / newline.
2058
+ *
2059
+ * In `partial` mode an unterminated string/object/array (or a value cut off at
2060
+ * end-of-input) is auto-closed with whatever was parsed so far — for streaming.
2061
+ * In strict mode, end-of-input mid-value and trailing garbage both throw, so a
2062
+ * final parse never silently accepts a half-formed tool call.
2063
+ */
2064
+ var RelaxedJson = class {
2065
+ #s;
2066
+ #n;
2067
+ #partial;
2068
+ #i = 0;
2069
+ constructor(source, partial) {
2070
+ this.#s = source;
2071
+ this.#n = source.length;
2072
+ this.#partial = partial;
2073
+ }
2074
+ parse() {
2075
+ this.#ws();
2076
+ if (this.#i >= this.#n) {
2077
+ if (this.#partial) return void 0;
2078
+ throw new SyntaxError("Unexpected end of JSON input");
2079
+ }
2080
+ const value = this.#value(false);
2081
+ if (value === INCOMPLETE) return void 0;
2082
+ this.#ws();
2083
+ if (!this.#partial && this.#i < this.#n) throw new SyntaxError(`Unexpected trailing characters at position ${this.#i}`);
2084
+ return value;
2085
+ }
2086
+ #ws() {
2087
+ const s = this.#s;
2088
+ for (;;) {
2089
+ while (this.#i < this.#n && isWhitespace(s.charCodeAt(this.#i))) this.#i++;
2090
+ if (this.#i + 1 < this.#n && s.charCodeAt(this.#i) === 47) {
2091
+ const next = s.charCodeAt(this.#i + 1);
2092
+ if (next === 47) {
2093
+ this.#i += 2;
2094
+ while (this.#i < this.#n && s.charCodeAt(this.#i) !== 10) this.#i++;
2095
+ continue;
2096
+ }
2097
+ if (next === 42) {
2098
+ this.#i += 2;
2099
+ while (this.#i + 1 < this.#n && !(s.charCodeAt(this.#i) === 42 && s.charCodeAt(this.#i + 1) === 47)) this.#i++;
2100
+ this.#i = Math.min(this.#i + 2, this.#n);
2101
+ continue;
2102
+ }
2103
+ }
2104
+ break;
2105
+ }
2106
+ }
2107
+ #value(allowBareword) {
2108
+ const s = this.#s;
2109
+ const c = s[this.#i];
2110
+ if (c === "{") return this.#object();
2111
+ if (c === "[") return this.#array();
2112
+ if (c === "\"" || c === "'") return this.#string(s.charCodeAt(this.#i));
2113
+ const cc = s.charCodeAt(this.#i);
2114
+ if (cc === 45 || cc === 43 || cc === 46 || cc >= 48 && cc <= 57) return this.#number();
2115
+ return this.#keyword(allowBareword);
2116
+ }
2117
+ #object() {
2118
+ this.#i++;
2119
+ const out = {};
2120
+ for (;;) {
2121
+ this.#ws();
2122
+ if (this.#i >= this.#n) {
2123
+ if (this.#partial) return out;
2124
+ throw new SyntaxError("Unterminated object");
2125
+ }
2126
+ const c = this.#s[this.#i];
2127
+ if (c === "}") {
2128
+ this.#i++;
2129
+ return out;
2130
+ }
2131
+ if (c === ",") {
2132
+ this.#i++;
2133
+ continue;
2134
+ }
2135
+ const key = this.#key();
2136
+ this.#ws();
2137
+ if (this.#i < this.#n && this.#s[this.#i] === ":") this.#i++;
2138
+ else if (this.#partial) return out;
2139
+ else throw new SyntaxError("Expected ':' in object");
2140
+ this.#ws();
2141
+ if (this.#i >= this.#n) {
2142
+ if (this.#partial) return out;
2143
+ throw new SyntaxError("Expected value after ':'");
2144
+ }
2145
+ const value = this.#value(true);
2146
+ if (value === INCOMPLETE) return out;
2147
+ out[key] = value;
2148
+ this.#ws();
2149
+ const d = this.#i < this.#n ? this.#s[this.#i] : "";
2150
+ if (d === ",") {
2151
+ this.#i++;
2152
+ continue;
2153
+ }
2154
+ if (d === "}") {
2155
+ this.#i++;
2156
+ return out;
2157
+ }
2158
+ if (this.#partial) return out;
2159
+ throw new SyntaxError("Expected ',' or '}' in object");
2160
+ }
2161
+ }
2162
+ #array() {
2163
+ this.#i++;
2164
+ const out = [];
2165
+ for (;;) {
2166
+ this.#ws();
2167
+ if (this.#i >= this.#n) {
2168
+ if (this.#partial) return out;
2169
+ throw new SyntaxError("Unterminated array");
2170
+ }
2171
+ const c = this.#s[this.#i];
2172
+ if (c === "]") {
2173
+ this.#i++;
2174
+ return out;
2175
+ }
2176
+ if (c === ",") {
2177
+ this.#i++;
2178
+ continue;
2179
+ }
2180
+ const value = this.#value(true);
2181
+ if (value === INCOMPLETE) return out;
2182
+ out.push(value);
2183
+ this.#ws();
2184
+ const d = this.#i < this.#n ? this.#s[this.#i] : "";
2185
+ if (d === ",") {
2186
+ this.#i++;
2187
+ continue;
2188
+ }
2189
+ if (d === "]") {
2190
+ this.#i++;
2191
+ return out;
2192
+ }
2193
+ if (this.#partial) return out;
2194
+ throw new SyntaxError("Expected ',' or ']' in array");
2195
+ }
2196
+ }
2197
+ #key() {
2198
+ const c = this.#s[this.#i];
2199
+ if (c === "\"" || c === "'") return this.#string(this.#s.charCodeAt(this.#i));
2200
+ const start = this.#i;
2201
+ while (this.#i < this.#n) {
2202
+ const ch = this.#s[this.#i];
2203
+ if (ch === ":" || ch === "," || ch === "}" || isWhitespace(this.#s.charCodeAt(this.#i))) break;
2204
+ this.#i++;
2205
+ }
2206
+ if (this.#i === start) {
2207
+ if (this.#partial) return "";
2208
+ throw new SyntaxError("Expected object key");
2209
+ }
2210
+ return this.#s.slice(start, this.#i);
2211
+ }
2212
+ #string(quote) {
2213
+ const s = this.#s;
2214
+ const n = this.#n;
2215
+ let i = this.#i + 1;
2216
+ let out = "";
2217
+ let runStart = i;
2218
+ while (i < n) {
2219
+ const cc = s.charCodeAt(i);
2220
+ if (cc !== BACKSLASH && cc !== quote) {
2221
+ i++;
2222
+ continue;
2223
+ }
2224
+ if (cc === quote) {
2225
+ if (!(quote === SQUOTE || this.#partial) || this.#closesString(i + 1)) {
2226
+ out += s.slice(runStart, i);
2227
+ this.#i = i + 1;
2228
+ return out;
2229
+ }
2230
+ i++;
2231
+ continue;
2232
+ }
2233
+ out += s.slice(runStart, i);
2234
+ i++;
2235
+ if (i >= n) {
2236
+ out += "\\";
2237
+ runStart = i;
2238
+ break;
2239
+ }
2240
+ switch (s.charCodeAt(i)) {
2241
+ case QUOTE:
2242
+ out += "\"";
2243
+ break;
2244
+ case SQUOTE:
2245
+ out += "'";
2246
+ break;
2247
+ case BACKSLASH:
2248
+ out += "\\";
2249
+ break;
2250
+ case 47:
2251
+ out += "/";
2252
+ break;
2253
+ case 98:
2254
+ out += "\b";
2255
+ break;
2256
+ case 102:
2257
+ out += "\f";
2258
+ break;
2259
+ case 110:
2260
+ out += "\n";
2261
+ break;
2262
+ case 114:
2263
+ out += "\r";
2264
+ break;
2265
+ case 116:
2266
+ out += " ";
2267
+ break;
2268
+ case U: {
2269
+ const hex = s.slice(i + 1, i + 5);
2270
+ if (HEX4_RE.test(hex)) {
2271
+ out += String.fromCharCode(parseInt(hex, 16));
2272
+ i += 4;
2273
+ } else out += "\\u";
2274
+ break;
2275
+ }
2276
+ default: out += `\\${s[i]}`;
2277
+ }
2278
+ i++;
2279
+ runStart = i;
2280
+ }
2281
+ out += s.slice(runStart, i);
2282
+ if (this.#partial) {
2283
+ this.#i = i;
2284
+ return out;
2285
+ }
2286
+ throw new SyntaxError("Unterminated string");
2287
+ }
2288
+ /** A quote closes a string only when the next non-space char ends a value. */
2289
+ #closesString(from) {
2290
+ const s = this.#s;
2291
+ let k = from;
2292
+ while (k < this.#n && isWhitespace(s.charCodeAt(k))) k++;
2293
+ if (k >= this.#n) return true;
2294
+ const c = s[k];
2295
+ return c === "," || c === "}" || c === "]" || c === ":";
2296
+ }
2297
+ #number() {
2298
+ const s = this.#s;
2299
+ const start = this.#i;
2300
+ while (this.#i < this.#n) {
2301
+ const ch = s[this.#i];
2302
+ if (ch >= "0" && ch <= "9" || ch === "-" || ch === "+" || ch === "." || ch === "e" || ch === "E" || ch === "x" || ch === "X" || ch >= "a" && ch <= "f" || ch >= "A" && ch <= "F") this.#i++;
2303
+ else break;
2304
+ }
2305
+ const token = s.slice(start, this.#i);
2306
+ const num = Number(token);
2307
+ if (Number.isNaN(num)) {
2308
+ if (this.#partial) return INCOMPLETE;
2309
+ throw new SyntaxError(`Invalid number: ${token}`);
2310
+ }
2311
+ return num;
2312
+ }
2313
+ #keyword(allowBareword) {
2314
+ const s = this.#s;
2315
+ const i = this.#i;
2316
+ for (const [word, value] of KEYWORDS) if (s.startsWith(word, i) && !isIdentChar(s.charCodeAt(i + word.length))) {
2317
+ this.#i += word.length;
2318
+ return value;
2319
+ }
2320
+ if (this.#partial) {
2321
+ this.#i = this.#n;
2322
+ return INCOMPLETE;
2323
+ }
2324
+ if (allowBareword) return this.#bareword();
2325
+ throw new SyntaxError(`Unexpected token at position ${this.#i}`);
2326
+ }
2327
+ /**
2328
+ * Strict-mode recovery of an unquoted string value, e.g.
2329
+ * `{"paths": packages/foo/*}`: consume until `,` / `}` / `]` / newline and
2330
+ * trim trailing whitespace. Recovery still throws — so a final parse never
2331
+ * accepts a half-formed or non-finite argument — when the token:
2332
+ * - hits end-of-input before a delimiter (truncated value);
2333
+ * - contains a `"`, `{`, `[`, or a key-like `:` — this parser accepts
2334
+ * unquoted keys, so a missed comma (`{"a": foo "b": 1}`, `{a: foo b: 1}`)
2335
+ * would otherwise silently swallow the following field. A colon followed
2336
+ * by `/` or `\` stays literal so URL and Windows-path values recover;
2337
+ * - is a non-finite atom ({@link NON_RECOVERABLE_BAREWORDS}).
2338
+ */
2339
+ #bareword() {
2340
+ const s = this.#s;
2341
+ const start = this.#i;
2342
+ let i = start;
2343
+ while (i < this.#n) {
2344
+ const cc = s.charCodeAt(i);
2345
+ if (cc === 44 || cc === 125 || cc === 93 || cc === 10 || cc === 13) break;
2346
+ if (cc === QUOTE || cc === 123 || cc === 91 || cc === 58 && s.charCodeAt(i + 1) !== 47 && s.charCodeAt(i + 1) !== 92) throw new SyntaxError(`Unexpected token at position ${start}`);
2347
+ i++;
2348
+ }
2349
+ if (i >= this.#n) throw new SyntaxError(`Unexpected token at position ${start}`);
2350
+ let end = i;
2351
+ while (end > start && isWhitespace(s.charCodeAt(end - 1))) end--;
2352
+ const word = s.slice(start, end);
2353
+ if (NON_RECOVERABLE_BAREWORDS[word]) throw new SyntaxError(`Unexpected token at position ${start}`);
2354
+ this.#i = i;
2355
+ return word;
2356
+ }
2357
+ };
2358
+ /**
2359
+ * Final-parse a JSON value, repairing the common LLM malformations
2360
+ * ({@link RelaxedJson}). Tries strict `JSON.parse` first (fast path, exact JSON
2361
+ * semantics), then the relaxed parser. Throws when the input is unrepairable,
2362
+ * truncated, or carries trailing garbage — so callers can skip a bad tool call
2363
+ * rather than execute a half-formed one.
2364
+ */
2365
+ function parseJsonWithRepair(json) {
2366
+ try {
2367
+ return JSON.parse(json);
2368
+ } catch {
2369
+ return new RelaxedJson(json, false).parse();
2370
+ }
2371
+ }
2372
+ /**
2373
+ * Parse possibly-incomplete JSON during streaming. Always returns a value, never
2374
+ * throws: `{}` for empty/whitespace/unrecoverable buffers, and an auto-closed
2375
+ * best-effort object for truncated ones.
2376
+ */
2377
+ function parseStreamingJson(partialJson) {
2378
+ const trimmed = partialJson?.trimStart();
2379
+ if (!trimmed) return {};
2380
+ try {
2381
+ return JSON.parse(trimmed);
2382
+ } catch {
2383
+ try {
2384
+ return new RelaxedJson(trimmed, true).parse() ?? {};
2385
+ } catch {
2386
+ return {};
2387
+ }
2388
+ }
2389
+ }
2390
+ /**
2391
+ * Throttled variant of {@link parseStreamingJson} for the per-delta hot path.
2392
+ *
2393
+ * Tool calls arrive as a long sequence of small deltas — calling
2394
+ * `parseStreamingJson(buffer)` on every delta re-parses the entire buffer
2395
+ * each time, giving O(N²) work in the total buffer length. A fixed re-parse
2396
+ * floor alone does NOT fix this: with `minGrowthBytes` constant, a buffer of
2397
+ * length N is parsed N/minGrowthBytes times at an average cost of N/2, which
2398
+ * is still O(N²) (the constant just shrinks). Long `write` payloads — where
2399
+ * the buffer is the whole file — made this the dominant main-thread stall
2400
+ * during streaming.
2401
+ *
2402
+ * Instead the gate scales geometrically: once the buffer is large, a re-parse
2403
+ * requires growth proportional to the current length (`len / 32`, floored at
2404
+ * `minGrowthBytes`). Parse points then form a geometric progression, so a
2405
+ * buffer of length N is parsed O(log N) times for O(N log N) total work,
2406
+ * while small buffers keep the snappy fixed-cadence updates.
2407
+ *
2408
+ * Each provider tracks the last parsed length on its tool-call block, so the
2409
+ * final `toolcall_end` parse (which providers already perform unconditionally)
2410
+ * is the authoritative full parse — the throttle only delays mid-stream UI
2411
+ * updates, by at most ~3% of the accumulated content for large buffers.
2412
+ *
2413
+ * @returns the parsed object plus the new `parsedLen` to persist; or `null`
2414
+ * when the buffer has not grown enough to warrant a re-parse.
2415
+ */
2416
+ function parseStreamingJsonThrottled(partialJson, lastParsedLen, minGrowthBytes = 256) {
2417
+ const len = partialJson?.length ?? 0;
2418
+ if (len === 0) return null;
2419
+ const growth = Math.max(minGrowthBytes, len >> 5);
2420
+ if (lastParsedLen > 0 && len - lastParsedLen < growth) return null;
2421
+ return {
2422
+ value: parseStreamingJson(partialJson),
2423
+ parsedLen: len
2424
+ };
2425
+ }
2426
+ /** What the strict-prefix scanner expects at the current position. */
2427
+ var JsonExpect = /* @__PURE__ */ function(JsonExpect) {
2428
+ JsonExpect[JsonExpect["Value"] = 0] = "Value";
2429
+ JsonExpect[JsonExpect["ObjKeyOrEnd"] = 1] = "ObjKeyOrEnd";
2430
+ JsonExpect[JsonExpect["ObjKey"] = 2] = "ObjKey";
2431
+ JsonExpect[JsonExpect["ObjColon"] = 3] = "ObjColon";
2432
+ JsonExpect[JsonExpect["ObjCommaOrEnd"] = 4] = "ObjCommaOrEnd";
2433
+ JsonExpect[JsonExpect["ArrValueOrEnd"] = 5] = "ArrValueOrEnd";
2434
+ JsonExpect[JsonExpect["ArrCommaOrEnd"] = 6] = "ArrCommaOrEnd";
2435
+ JsonExpect[JsonExpect["End"] = 7] = "End";
2436
+ return JsonExpect;
2437
+ }(JsonExpect || {});
2438
+ /**
2439
+ * Classify `text` as a strict-JSON value, prefix, or dead end.
2440
+ *
2441
+ * Providers use this to disambiguate identifierless streaming tool-call
2442
+ * deltas: a chunk starting with `{` is a *new* sibling call only if the
2443
+ * current call's argument buffer cannot absorb it — the buffer is already a
2444
+ * complete value, already unsalvageable (lossy hosts abandon buffers
2445
+ * mid-string, leaving raw control characters strict JSON forbids), or the
2446
+ * concatenation would break it. Unlike {@link parseStreamingJson} this is
2447
+ * deliberately strict: forgiving repair would mask exactly the corruption
2448
+ * signals the caller needs.
2449
+ *
2450
+ * A top-level number at end-of-input classifies as `"complete"` even though
2451
+ * more digits could extend it; tool-argument buffers are always objects, so
2452
+ * the ambiguity is immaterial here.
2453
+ */
2454
+ function classifyJsonPrefix(text) {
2455
+ const n = text.length;
2456
+ let i = 0;
2457
+ const stack = [];
2458
+ let expect = 0;
2459
+ /** Consume a string starting at the opening quote. 1 = ok, 0 = prefix, -1 = invalid. */
2460
+ const scanString = () => {
2461
+ i++;
2462
+ while (i < n) {
2463
+ const c = text.charCodeAt(i);
2464
+ if (c === QUOTE) {
2465
+ i++;
2466
+ return 1;
2467
+ }
2468
+ if (c === BACKSLASH) {
2469
+ i++;
2470
+ if (i >= n) return 0;
2471
+ const e = text.charCodeAt(i);
2472
+ if (e >= 128 || !VALID_ESCAPE_CHAR[e]) return -1;
2473
+ i++;
2474
+ if (e === U) for (let k = 0; k < 4; k++, i++) {
2475
+ if (i >= n) return 0;
2476
+ if (!isHexDigit(text.charCodeAt(i))) return -1;
2477
+ }
2478
+ continue;
2479
+ }
2480
+ if (c < 32) return -1;
2481
+ i++;
2482
+ }
2483
+ return 0;
2484
+ };
2485
+ /** Consume a number starting at `-` or a digit. 1 = token done, 0 = prefix, -1 = invalid. */
2486
+ const scanNumber = () => {
2487
+ if (text.charCodeAt(i) === 45) i++;
2488
+ if (i >= n) return 0;
2489
+ let c = text.charCodeAt(i);
2490
+ if (c === 48) i++;
2491
+ else if (c >= 49 && c <= 57) while (i < n && text.charCodeAt(i) >= 48 && text.charCodeAt(i) <= 57) i++;
2492
+ else return -1;
2493
+ if (i < n && text.charCodeAt(i) === 46) {
2494
+ i++;
2495
+ if (i >= n) return 0;
2496
+ if (text.charCodeAt(i) < 48 || text.charCodeAt(i) > 57) return -1;
2497
+ while (i < n && text.charCodeAt(i) >= 48 && text.charCodeAt(i) <= 57) i++;
2498
+ }
2499
+ c = i < n ? text.charCodeAt(i) : 0;
2500
+ if (c === 101 || c === 69) {
2501
+ i++;
2502
+ if (i < n && (text.charCodeAt(i) === 43 || text.charCodeAt(i) === 45)) i++;
2503
+ if (i >= n) return 0;
2504
+ if (text.charCodeAt(i) < 48 || text.charCodeAt(i) > 57) return -1;
2505
+ while (i < n && text.charCodeAt(i) >= 48 && text.charCodeAt(i) <= 57) i++;
2506
+ }
2507
+ return 1;
2508
+ };
2509
+ /** Consume `true`/`false`/`null`. 1 = done, 0 = prefix, -1 = invalid. */
2510
+ const scanKeyword = () => {
2511
+ for (const word of [
2512
+ "true",
2513
+ "false",
2514
+ "null"
2515
+ ]) {
2516
+ if (word.charCodeAt(0) !== text.charCodeAt(i)) continue;
2517
+ const available = Math.min(word.length, n - i);
2518
+ if (!word.startsWith(text.slice(i, i + available))) return -1;
2519
+ i += available;
2520
+ return available === word.length ? 1 : 0;
2521
+ }
2522
+ return -1;
2523
+ };
2524
+ /** A value just finished; the next expectation follows from the stack. */
2525
+ const valueDone = () => stack.length === 0 ? 7 : stack[stack.length - 1] ? 4 : 6;
2526
+ while (i < n) {
2527
+ const c = text.charCodeAt(i);
2528
+ if (isWhitespace(c)) {
2529
+ i++;
2530
+ continue;
2531
+ }
2532
+ switch (expect) {
2533
+ case 0:
2534
+ case 5: {
2535
+ if (c === 93 && expect === 5) {
2536
+ stack.pop();
2537
+ i++;
2538
+ expect = valueDone();
2539
+ break;
2540
+ }
2541
+ if (c === 123) {
2542
+ stack.push(true);
2543
+ i++;
2544
+ expect = 1;
2545
+ break;
2546
+ }
2547
+ if (c === 91) {
2548
+ stack.push(false);
2549
+ i++;
2550
+ expect = 5;
2551
+ break;
2552
+ }
2553
+ let r;
2554
+ if (c === QUOTE) r = scanString();
2555
+ else if (c === 45 || c >= 48 && c <= 57) r = scanNumber();
2556
+ else if (c === 116 || c === 102 || c === 110) r = scanKeyword();
2557
+ else return "invalid";
2558
+ if (r === -1) return "invalid";
2559
+ if (r === 0) return "prefix";
2560
+ expect = valueDone();
2561
+ break;
2562
+ }
2563
+ case 1:
2564
+ case 2: {
2565
+ if (c === 125 && expect === 1) {
2566
+ stack.pop();
2567
+ i++;
2568
+ expect = valueDone();
2569
+ break;
2570
+ }
2571
+ if (c !== QUOTE) return "invalid";
2572
+ const r = scanString();
2573
+ if (r === -1) return "invalid";
2574
+ if (r === 0) return "prefix";
2575
+ expect = 3;
2576
+ break;
2577
+ }
2578
+ case 3:
2579
+ if (c !== 58) return "invalid";
2580
+ i++;
2581
+ expect = 0;
2582
+ break;
2583
+ case 4:
2584
+ if (c === 125) {
2585
+ stack.pop();
2586
+ i++;
2587
+ expect = valueDone();
2588
+ break;
2589
+ }
2590
+ if (c !== 44) return "invalid";
2591
+ i++;
2592
+ expect = 2;
2593
+ break;
2594
+ case 6:
2595
+ if (c === 93) {
2596
+ stack.pop();
2597
+ i++;
2598
+ expect = valueDone();
2599
+ break;
2600
+ }
2601
+ if (c !== 44) return "invalid";
2602
+ i++;
2603
+ expect = 0;
2604
+ break;
2605
+ case 7: return "invalid";
2606
+ }
2607
+ }
2608
+ return expect === 7 ? "complete" : "prefix";
2609
+ }
2610
+ new Intl.Segmenter();
2611
+ //#endregion
2612
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/mime.ts
2613
+ const DEFAULT_IMAGE_METADATA_HEADER_BYTES = 262144;
2614
+ const PNG_MAGIC = Buffer.from([
2615
+ 137,
2616
+ 80,
2617
+ 78,
2618
+ 71,
2619
+ 13,
2620
+ 10,
2621
+ 26,
2622
+ 10
2623
+ ]);
2624
+ const JPEG_MAGIC = Buffer.from([
2625
+ 255,
2626
+ 216,
2627
+ 255
2628
+ ]);
2629
+ const WEBP_RIFF_MAGIC = Buffer.from([
2630
+ 82,
2631
+ 73,
2632
+ 70,
2633
+ 70
2634
+ ]);
2635
+ const WEBP_MAGIC = Buffer.from([
2636
+ 87,
2637
+ 69,
2638
+ 66,
2639
+ 80
2640
+ ]);
2641
+ const PNG_IHDR = Buffer.from("IHDR");
2642
+ const GIF87A = Buffer.from("GIF87a");
2643
+ const GIF89A = Buffer.from("GIF89a");
2644
+ const WEBP_VP8X = Buffer.from("VP8X");
2645
+ const WEBP_VP8L = Buffer.from("VP8L");
2646
+ const WEBP_VP8 = Buffer.from("VP8 ");
2647
+ function magicEquals(header, offset, magic) {
2648
+ if (header.length < offset + magic.length) return false;
2649
+ return magic.equals(header.subarray(offset, offset + magic.length));
2650
+ }
2651
+ function parsePngMetadata(header) {
2652
+ if (!magicEquals(header, 0, PNG_MAGIC)) return null;
2653
+ if (!magicEquals(header, 12, PNG_IHDR)) return { mimeType: "image/png" };
2654
+ if (header.length < 26) return { mimeType: "image/png" };
2655
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
2656
+ const width = view.getUint32(16, false);
2657
+ const height = view.getUint32(20, false);
2658
+ const colorType = view.getUint8(25);
2659
+ if (colorType === 0) return {
2660
+ mimeType: "image/png",
2661
+ width,
2662
+ height,
2663
+ channels: 1,
2664
+ hasAlpha: false
2665
+ };
2666
+ if (colorType === 2) return {
2667
+ mimeType: "image/png",
2668
+ width,
2669
+ height,
2670
+ channels: 3,
2671
+ hasAlpha: false
2672
+ };
2673
+ if (colorType === 3) return {
2674
+ mimeType: "image/png",
2675
+ width,
2676
+ height,
2677
+ channels: 3
2678
+ };
2679
+ if (colorType === 4) return {
2680
+ mimeType: "image/png",
2681
+ width,
2682
+ height,
2683
+ channels: 2,
2684
+ hasAlpha: true
2685
+ };
2686
+ if (colorType === 6) return {
2687
+ mimeType: "image/png",
2688
+ width,
2689
+ height,
2690
+ channels: 4,
2691
+ hasAlpha: true
2692
+ };
2693
+ return {
2694
+ mimeType: "image/png",
2695
+ width,
2696
+ height
2697
+ };
2698
+ }
2699
+ function parseJpegMetadata(header) {
2700
+ if (!magicEquals(header, 0, JPEG_MAGIC)) return null;
2701
+ if (header.length < 4) return { mimeType: "image/jpeg" };
2702
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
2703
+ let offset = 2;
2704
+ while (offset + 9 < header.length) {
2705
+ if (header[offset] !== 255) {
2706
+ offset += 1;
2707
+ continue;
2708
+ }
2709
+ let markerOffset = offset + 1;
2710
+ while (markerOffset < header.length && header[markerOffset] === 255) markerOffset += 1;
2711
+ if (markerOffset >= header.length) break;
2712
+ const marker = header[markerOffset];
2713
+ const segmentOffset = markerOffset + 1;
2714
+ if (marker === 216 || marker === 217 || marker === 1 || marker >= 208 && marker <= 215) {
2715
+ offset = segmentOffset;
2716
+ continue;
2717
+ }
2718
+ if (segmentOffset + 1 >= header.length) break;
2719
+ const segmentLength = view.getUint16(segmentOffset, false);
2720
+ if (segmentLength < 2) break;
2721
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
2722
+ if (segmentOffset + 7 >= header.length) break;
2723
+ const height = view.getUint16(segmentOffset + 3, false);
2724
+ const width = view.getUint16(segmentOffset + 5, false);
2725
+ const channels = header[segmentOffset + 7];
2726
+ return {
2727
+ mimeType: "image/jpeg",
2728
+ width,
2729
+ height,
2730
+ channels: Number.isFinite(channels) ? channels : void 0,
2731
+ hasAlpha: false
2732
+ };
2733
+ }
2734
+ offset = segmentOffset + segmentLength;
2735
+ }
2736
+ return { mimeType: "image/jpeg" };
2737
+ }
2738
+ function parseGifMetadata(header) {
2739
+ if (!magicEquals(header, 0, GIF87A) && !magicEquals(header, 0, GIF89A)) return null;
2740
+ if (header.length < 10) return { mimeType: "image/gif" };
2741
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
2742
+ return {
2743
+ mimeType: "image/gif",
2744
+ width: view.getUint16(6, true),
2745
+ height: view.getUint16(8, true),
2746
+ channels: 3
2747
+ };
2748
+ }
2749
+ function parseWebpMetadata(header) {
2750
+ if (!magicEquals(header, 0, WEBP_RIFF_MAGIC)) return null;
2751
+ if (!magicEquals(header, 8, WEBP_MAGIC)) return null;
2752
+ if (header.length < 30) return { mimeType: "image/webp" };
2753
+ if (magicEquals(header, 12, WEBP_VP8X)) {
2754
+ const hasAlpha = (header[20] & 16) !== 0;
2755
+ return {
2756
+ mimeType: "image/webp",
2757
+ width: (header[24] | header[25] << 8 | header[26] << 16) + 1,
2758
+ height: (header[27] | header[28] << 8 | header[29] << 16) + 1,
2759
+ channels: hasAlpha ? 4 : 3,
2760
+ hasAlpha
2761
+ };
2762
+ }
2763
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
2764
+ if (magicEquals(header, 12, WEBP_VP8L)) {
2765
+ if (header.length < 25) return { mimeType: "image/webp" };
2766
+ const bits = view.getUint32(21, true);
2767
+ const width = (bits & 16383) + 1;
2768
+ const height = (bits >> 14 & 16383) + 1;
2769
+ const hasAlpha = (bits >> 28 & 1) === 1;
2770
+ return {
2771
+ mimeType: "image/webp",
2772
+ width,
2773
+ height,
2774
+ channels: hasAlpha ? 4 : 3,
2775
+ hasAlpha
2776
+ };
2777
+ }
2778
+ if (magicEquals(header, 12, WEBP_VP8)) return {
2779
+ mimeType: "image/webp",
2780
+ width: view.getUint16(26, true) & 16383,
2781
+ height: view.getUint16(28, true) & 16383,
2782
+ channels: 3,
2783
+ hasAlpha: false
2784
+ };
2785
+ return { mimeType: "image/webp" };
2786
+ }
2787
+ function parseImageMetadata(header) {
2788
+ return parsePngMetadata(header) ?? parseJpegMetadata(header) ?? parseGifMetadata(header) ?? parseWebpMetadata(header);
2789
+ }
2790
+ function readImageMetadata(filePath, maxBytes = DEFAULT_IMAGE_METADATA_HEADER_BYTES) {
2791
+ return peekFile(filePath, maxBytes, parseImageMetadata);
2792
+ }
2793
+ //#endregion
2794
+ //#region src/tools/shared/bun-ffi-shim.ts
2795
+ /**
2796
+ * DSH named-import shim for `bun:ffi` (pi-utils stderr-guard / process-name).
2797
+ * Both consumers degrade silently when dlopen is unavailable ("Never throws:
2798
+ * bun:ffi unavailability ... degrades silently"), so this shim returns null.
2799
+ */
2800
+ const FFIType = {};
2801
+ function dlopen() {
2802
+ return null;
2803
+ }
2804
+ //#endregion
2805
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/stderr-guard.ts
2806
+ /**
2807
+ * Terminal stderr guard: keeps unmanaged fd-2 writes off the terminal while a
2808
+ * TUI owns the viewport.
2809
+ *
2810
+ * On macOS, runtime diagnostics are written by the platform directly to file
2811
+ * descriptor 2 at arbitrary times — e.g. libmalloc's "MallocStackLogging:
2812
+ * can't turn off malloc stack logging because it was not enabled" when the OS
2813
+ * broadcasts a memory-diagnostic event to long-lived processes. Those bytes
2814
+ * bypass the renderer and paint straight into the viewport. Stripping the
2815
+ * MallocStackLogging* env vars (cli.ts) only protects child processes; it
2816
+ * cannot stop libmalloc inside THIS process from logging.
2817
+ *
2818
+ * Fix (mirrors openai/codex#24459): while the TUI owns the terminal, dup fd 2
2819
+ * aside and dup2 a redirect target over it; restore the saved fd whenever
2820
+ * terminal ownership is released (external editor, Ctrl+Z suspend, shutdown,
2821
+ * crash restore). Unlike codex we redirect to the omp log file — not
2822
+ * /dev/null — so the diagnostics stay greppable and Bun native-crash reports
2823
+ * (which abort before any JS cleanup can restore fd 2) are preserved.
2824
+ *
2825
+ * Only dup/dup2 go through bun:ffi. fcntl is deliberately avoided: it is
2826
+ * variadic, and the arm64-darwin ABI passes variadic arguments on the stack,
2827
+ * so a fixed-arity FFI signature would read garbage for the third argument.
2828
+ */
2829
+ const STDERR_FILENO = 2;
2830
+ let libcFdOpsCache;
2831
+ function libcFdOps() {
2832
+ if (libcFdOpsCache !== void 0) return libcFdOpsCache;
2833
+ libcFdOpsCache = null;
2834
+ if (process.platform === "win32") return null;
2835
+ const candidates = process.platform === "darwin" ? ["libSystem.B.dylib", "/usr/lib/libSystem.B.dylib"] : ["libc.so.6", "libc.so"];
2836
+ for (const candidate of candidates) try {
2837
+ libcFdOpsCache = dlopen(candidate, {
2838
+ dup: {
2839
+ args: [FFIType.i32],
2840
+ returns: FFIType.i32
2841
+ },
2842
+ dup2: {
2843
+ args: [FFIType.i32, FFIType.i32],
2844
+ returns: FFIType.i32
2845
+ }
2846
+ }).symbols;
2847
+ return libcFdOpsCache;
2848
+ } catch {}
2849
+ return libcFdOpsCache;
2850
+ }
2851
+ /** Saved dup of the real stderr while suppression is active, else null. */
2852
+ let savedStderrFd = null;
2853
+ /**
2854
+ * Re-point fd 2 at the saved terminal stderr. Safe to call unconditionally:
2855
+ * no-op when suppression is not active. Called at every terminal-ownership
2856
+ * release and by the postmortem fatal handlers before they print, so crash
2857
+ * reports reach the real terminal.
2858
+ */
2859
+ function restoreTerminalStderr() {
2860
+ if (savedStderrFd === null) return;
2861
+ const saved = savedStderrFd;
2862
+ savedStderrFd = null;
2863
+ libcFdOps()?.dup2(saved, STDERR_FILENO);
2864
+ try {
2865
+ fs.closeSync(saved);
2866
+ } catch {}
2867
+ }
2868
+ const callbackList = [];
2869
+ let cleanupStage = "idle";
2870
+ const CLEANUP_DEADLINE_MS = 1e4;
2871
+ /**
2872
+ * Symbol stamped by the extension-load guard onto the throwing replacement it
2873
+ * installs over `process.exit` / `process.reallyExit`, carrying the native
2874
+ * primitive that replacement shadows.
2875
+ *
2876
+ * Host-owned shutdown ({@link exitProcess}) reads through it so a signal that
2877
+ * lands while the guard is active still terminates the process (#6488), while
2878
+ * a signal that lands after the guard has restored the native exit also
2879
+ * terminates cleanly (#7393). `Symbol.for` so it survives duplicate module
2880
+ * instances across bundles/realms.
2881
+ */
2882
+ const NATIVE_PROCESS_EXIT = Symbol.for("omp.postmortem.nativeProcessExit");
2883
+ /**
2884
+ * Hard-exit the process through the native primitive, resolved on every call.
2885
+ *
2886
+ * The native exit is deliberately re-resolved here rather than bound at module
2887
+ * load: the extension/hook loader's `withHostGuard` transiently swaps
2888
+ * `process.reallyExit`/`process.exit` for a stub that throws
2889
+ * `ExtensionExitError`, and the shipped bundle defers this module's evaluation
2890
+ * until first access — which can land inside that guard window, so binding at
2891
+ * init could freeze the throwing stub forever and turn every later shutdown
2892
+ * (SIGHUP/SIGINT/fatal) into an unhandled-rejection loop (#7393). When the
2893
+ * guard is active the stub carries the native exit under
2894
+ * {@link NATIVE_PROCESS_EXIT}; unwrapping it lets a mid-guard signal still exit
2895
+ * (#6488). Otherwise the current `process.reallyExit`/`process.exit` is native.
2896
+ */
2897
+ function exitProcess(code) {
2898
+ const current = typeof process.reallyExit === "function" ? process.reallyExit : process.exit;
2899
+ const behind = Reflect.get(current, NATIVE_PROCESS_EXIT);
2900
+ return (typeof behind === "function" ? behind : current).call(process, code);
2901
+ }
2902
+ let cleanupPromise;
2903
+ const fatalRecoveryHintProviders = /* @__PURE__ */ new Set();
2904
+ /**
2905
+ * Internal: runs all registered cleanup callbacks for the given reason.
2906
+ * Ensures each callback is invoked at most once. Handles errors and prevents reentrancy.
2907
+ *
2908
+ * Returns a Promise that settles after all cleanups complete or error out.
2909
+ */
2910
+ function runCleanup(reason) {
2911
+ switch (cleanupStage) {
2912
+ case "idle":
2913
+ cleanupStage = "running";
2914
+ break;
2915
+ case "running": return cleanupPromise ?? Promise.resolve();
2916
+ case "complete": return Promise.resolve();
2917
+ }
2918
+ const promises = callbackList.toReversed().map((callback) => {
2919
+ return Promise.try(() => callback(reason));
2920
+ });
2921
+ const cleanupSettled = Promise.allSettled(promises).then((results) => {
2922
+ for (const result of results) if (result.status === "rejected") {
2923
+ const err = result.reason instanceof Error ? result.reason : new Error(String(result.reason));
2924
+ error("Cleanup callback failed", {
2925
+ err,
2926
+ stack: err.stack
2927
+ });
2928
+ }
2929
+ cleanupStage = "complete";
2930
+ });
2931
+ const deadline = Promise.withResolvers();
2932
+ const deadlineTimer = setTimeout(() => {
2933
+ error("Cleanup deadline exceeded; proceeding with exit", { reason });
2934
+ cleanupStage = "complete";
2935
+ deadline.resolve();
2936
+ }, CLEANUP_DEADLINE_MS);
2937
+ cleanupPromise = Promise.race([cleanupSettled, deadline.promise]).finally(() => {
2938
+ clearTimeout(deadlineTimer);
2939
+ });
2940
+ return cleanupPromise;
2941
+ }
2942
+ let inspectorOpened = false;
2943
+ /**
2944
+ * Classify EPIPE errors from worker IPC and stdio without treating unrelated
2945
+ * broken pipes as globally recoverable.
2946
+ */
2947
+ function classifyBrokenPipe(err) {
2948
+ if (!("code" in err) || err.code !== "EPIPE" || !("syscall" in err)) return void 0;
2949
+ if (err.syscall === "send") return "ipc-send";
2950
+ if (err.syscall === "write") return "stdio-write";
2951
+ }
2952
+ const EXPECTED_CLEANUP = Symbol.for("omp.expectedCleanupError");
2953
+ /**
2954
+ * Whether `reason` (or any error in its `cause` chain) was marked via
2955
+ * {@link markExpectedCleanupError}. Walks the chain because the unhandled
2956
+ * reason is often a wrapper (`AbortError`) with the marked abort reason as
2957
+ * its `cause`.
2958
+ */
2959
+ function isExpectedCleanupError(reason) {
2960
+ let current = reason;
2961
+ for (let depth = 0; depth < 8 && current !== null && typeof current === "object"; depth++) {
2962
+ if (current[EXPECTED_CLEANUP] === true) return true;
2963
+ current = current.cause;
2964
+ }
2965
+ return false;
2966
+ }
2967
+ /** Interceptors consulted by the global `unhandledRejection` handler before the fatal path. */
2968
+ const rejectionInterceptors = /* @__PURE__ */ new Set();
2969
+ function escapeFatalHintText(value) {
2970
+ return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, (char) => {
2971
+ return `\\u${(char.codePointAt(0) ?? 0).toString(16).padStart(4, "0")}`;
2972
+ });
2973
+ }
2974
+ function formatFatalRecoveryHints() {
2975
+ const lines = [];
2976
+ const seenCommands = /* @__PURE__ */ new Set();
2977
+ for (const provider of fatalRecoveryHintProviders) try {
2978
+ const hint = provider();
2979
+ if (!hint?.command || seenCommands.has(hint.command)) continue;
2980
+ seenCommands.add(hint.command);
2981
+ lines.push(` ${escapeFatalHintText(hint.label)}: ${escapeFatalHintText(hint.command)}`);
2982
+ } catch (err) {
2983
+ warn("Fatal recovery hint provider failed", { err });
2984
+ }
2985
+ return lines.length > 0 ? `\n[Recovery]\n${lines.join("\n")}\n` : "";
2986
+ }
2987
+ function formatFatalError(label, err) {
2988
+ const name = err.name || "Error";
2989
+ const message = err.message || "(no message)";
2990
+ const stackLines = (err.stack || "").split("\n").slice(1);
2991
+ return `\n[${label}] ${name}: ${message}${stackLines.length > 0 ? `\n${stackLines.join("\n")}` : ""}\n`;
2992
+ }
2993
+ async function exitAfterFatal(label, logMessage, err, reason) {
2994
+ const forcedExit = setTimeout(() => exitProcess(1), CLEANUP_DEADLINE_MS);
2995
+ try {
2996
+ restoreTerminalStderr();
2997
+ try {
2998
+ fs.writeSync(2, `${formatFatalError(label, err)}${formatFatalRecoveryHints()}`);
2999
+ } catch {}
3000
+ error(logMessage, { err });
3001
+ await runCleanup(reason);
3002
+ } finally {
3003
+ clearTimeout(forcedExit);
3004
+ exitProcess(1);
3005
+ }
3006
+ }
3007
+ if (isMainThread) process.on("SIGINT", async () => {
3008
+ await runCleanup("sigint");
3009
+ exitProcess(130);
3010
+ }).on("SIGUSR1", () => {
3011
+ if (inspectorOpened) return;
3012
+ inspectorOpened = true;
3013
+ inspector.open(void 0, void 0, false);
3014
+ const url = inspector.url();
3015
+ process.stderr.write(`Inspector opened: ${url}\n`);
3016
+ }).on("uncaughtException", async (err) => {
3017
+ if (isExpectedCleanupError(err)) {
3018
+ warn("Ignoring expected cleanup exception", { err });
3019
+ return;
3020
+ }
3021
+ await exitAfterFatal("Uncaught Exception", "Uncaught exception", err, "uncaught_exception");
3022
+ }).on("unhandledRejection", async (reason) => {
3023
+ const err = reason instanceof Error ? reason : new Error(String(reason));
3024
+ if (classifyBrokenPipe(err) === "ipc-send") {
3025
+ warn("Ignoring EPIPE from worker IPC send; optional subsystem will self-recover", { err });
3026
+ return;
3027
+ }
3028
+ if (isExpectedCleanupError(reason)) {
3029
+ warn("Ignoring expected cleanup rejection", { err });
3030
+ return;
3031
+ }
3032
+ for (const interceptor of rejectionInterceptors) try {
3033
+ if (interceptor(reason)) return;
3034
+ } catch (interceptorErr) {
3035
+ warn("Unhandled-rejection interceptor threw; continuing with fatal path", { err: interceptorErr });
3036
+ }
3037
+ await exitAfterFatal("Unhandled Rejection", "Unhandled rejection", err, "unhandled_rejection");
3038
+ }).on("exit", async () => {
3039
+ runCleanup("exit");
3040
+ }).on("SIGTERM", async () => {
3041
+ await runCleanup("sigterm");
3042
+ exitProcess(143);
3043
+ }).on("SIGHUP", async () => {
3044
+ await runCleanup("sighup");
3045
+ exitProcess(129);
3046
+ });
3047
+ else process.on("exit", () => {
3048
+ runCleanup("exit");
3049
+ });
3050
+ //#endregion
3051
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/which.ts
3052
+ const XCODE_BINS = /* @__PURE__ */ new Set([
3053
+ "clang",
3054
+ "clang++",
3055
+ "gcc",
3056
+ "g++",
3057
+ "cc",
3058
+ "c++",
3059
+ "cpp",
3060
+ "c89",
3061
+ "c99",
3062
+ "swift",
3063
+ "swiftc",
3064
+ "swift-frontend",
3065
+ "clangd",
3066
+ "sourcekit-lsp",
3067
+ "ld",
3068
+ "ld-classic",
3069
+ "ar",
3070
+ "ranlib",
3071
+ "libtool",
3072
+ "as",
3073
+ "lipo",
3074
+ "install_name_tool",
3075
+ "codesign_allocate",
3076
+ "make",
3077
+ "gnumake",
3078
+ "m4",
3079
+ "flex",
3080
+ "bison",
3081
+ "yacc",
3082
+ "lex",
3083
+ "git",
3084
+ "git-receive-pack",
3085
+ "git-upload-pack",
3086
+ "git-upload-archive",
3087
+ "git-shell",
3088
+ "scalar",
3089
+ "lldb",
3090
+ "lldb-dap",
3091
+ "nm",
3092
+ "otool",
3093
+ "objdump",
3094
+ "strings",
3095
+ "strip",
3096
+ "size",
3097
+ "dsymutil",
3098
+ "dwarfdump",
3099
+ "lipo",
3100
+ "vtool",
3101
+ "clang-format",
3102
+ "swift-format"
3103
+ ]);
3104
+ const XCODE_BIN_PREFIXES = [
3105
+ "python",
3106
+ "pip",
3107
+ "pydoc",
3108
+ "2to3"
3109
+ ];
3110
+ function isXcodeBin(command) {
3111
+ if (XCODE_BINS.has(command)) return true;
3112
+ for (const prefix of XCODE_BIN_PREFIXES) if (command.startsWith(prefix)) return true;
3113
+ return false;
3114
+ }
3115
+ function getDeveloperDirs() {
3116
+ const envDir = process.env.DEVELOPER_DIR;
3117
+ if (envDir && fs.existsSync(envDir)) return envDir;
3118
+ try {
3119
+ return fs.readlinkSync("/var/db/xcode_select_link");
3120
+ } catch {}
3121
+ for (const candidate of ["/Applications/Xcode.app/Contents/Developer", "/Library/Developer/CommandLineTools"]) if (fs.existsSync(candidate)) return candidate;
3122
+ return null;
3123
+ }
3124
+ let macosToolPaths;
3125
+ function getMacosToolPaths() {
3126
+ if (macosToolPaths) return macosToolPaths;
3127
+ const paths = ["/Library/Developer/CommandLineTools/usr/bin"];
3128
+ const devDir = getDeveloperDirs();
3129
+ if (devDir) paths.push(path.join(devDir, "usr/bin"), path.join(devDir, "Toolchains/XcodeDefault.xctoolchain/usr/bin"));
3130
+ macosToolPaths = /* @__PURE__ */ new Map();
3131
+ for (const dir of Array.from(new Set(paths))) try {
3132
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) if (entry.isFile() || entry.isSymbolicLink()) {
3133
+ if (macosToolPaths.has(entry.name)) continue;
3134
+ macosToolPaths.set(entry.name, path.join(dir, entry.name));
3135
+ }
3136
+ } catch {}
3137
+ return macosToolPaths;
3138
+ }
3139
+ const toolCache = /* @__PURE__ */ new Map();
3140
+ function darwinWhich(command, options) {
3141
+ const regular = Bun.which(command, options);
3142
+ if (regular) return regular;
3143
+ if (isXcodeBin(command)) return getMacosToolPaths().get(command) ?? null;
3144
+ return null;
3145
+ }
3146
+ const whichFresh = os.platform() === "darwin" ? darwinWhich : Bun.which;
3147
+ function cacheKey$1(command, options) {
3148
+ if (!options) return command;
3149
+ if (!options.cwd && !options.PATH) return command;
3150
+ let h = Bun.hash(command);
3151
+ if (options.cwd) h = Bun.hash(options.cwd, h);
3152
+ if (options.PATH) h = Bun.hash(options.PATH, h);
3153
+ return h;
3154
+ }
3155
+ /**
3156
+ * Locate binary on PATH (with flexible caching).
3157
+ *
3158
+ * @param command - Binary name to resolve
3159
+ * @param options - Bun.WhichOptions plus `cache` control
3160
+ * @returns Filesystem path if found, else null
3161
+ */
3162
+ function $which(command, options) {
3163
+ const cachePolicy = options?.cache ?? 0;
3164
+ const lookupOptions = options?.PATH !== void 0 || process.env.PATH === void 0 ? options : {
3165
+ ...options,
3166
+ PATH: process.env.PATH
3167
+ };
3168
+ let key;
3169
+ if (cachePolicy !== 1) {
3170
+ key = cacheKey$1(command, lookupOptions);
3171
+ if (cachePolicy !== 2) {
3172
+ const cached = toolCache.get(key);
3173
+ if (cached !== void 0) return cached;
3174
+ }
3175
+ }
3176
+ const result = whichFresh(command, lookupOptions);
3177
+ if (key != null && cachePolicy !== 3) toolCache.set(key, result);
3178
+ return result;
3179
+ }
3180
+ //#endregion
3181
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/template.ts
3182
+ /** A string that bypasses HTML escaping when interpolated. */
3183
+ var SafeString = class {
3184
+ #value;
3185
+ constructor(value) {
3186
+ this.#value = String(value);
3187
+ }
3188
+ /** Return the unescaped string value. */
3189
+ toString() {
3190
+ return this.#value;
3191
+ }
3192
+ /** Return the unescaped primitive value. */
3193
+ toHTML() {
3194
+ return this.#value;
3195
+ }
3196
+ };
3197
+ const helpers = /* @__PURE__ */ new Map();
3198
+ const partials = /* @__PURE__ */ new Map();
3199
+ const BLOCK_TAG = /^\s*\{\{(?:#|\/|\^|else\b|!)/;
3200
+ function stripStandalone(source) {
3201
+ const lines = source.split(/(?<=\n)/);
3202
+ for (let index = 0; index < lines.length; index++) {
3203
+ const line = lines[index];
3204
+ const body = line.endsWith("\n") ? line.slice(0, -1).replace(/\r$/, "") : line;
3205
+ if (/^\s*\{\{!--/.test(body) && !body.includes("--}}")) {
3206
+ for (let end = index + 1; end < lines.length; end++) {
3207
+ const endLine = lines[end].endsWith("\n") ? lines[end].slice(0, -1).replace(/\r$/, "") : lines[end];
3208
+ const close = endLine.indexOf("--}}");
3209
+ if (close < 0) continue;
3210
+ if (/^\s*$/.test(endLine.slice(close + 4))) {
3211
+ for (let commentLine = index; commentLine <= end; commentLine++) lines[commentLine] = "";
3212
+ index = end;
3213
+ }
3214
+ break;
3215
+ }
3216
+ continue;
3217
+ }
3218
+ if (BLOCK_TAG.test(body) && /^\s*\{\{(?:#|\/|\^|else\b|!)[^{}]*\}\}\s*$/.test(body)) lines[index] = body.trim();
3219
+ }
3220
+ return lines.join("");
3221
+ }
3222
+ function tokenizeExpression(source) {
3223
+ const tokens = [];
3224
+ let start = -1;
3225
+ let quote = "";
3226
+ let depth = 0;
3227
+ let bracketDepth = 0;
3228
+ for (let index = 0; index < source.length; index++) {
3229
+ const char = source[index];
3230
+ if (quote) {
3231
+ if (char === "\\" && index + 1 < source.length) index++;
3232
+ else if (char === quote) quote = "";
3233
+ continue;
3234
+ }
3235
+ if (char === "\"" || char === "'") {
3236
+ if (start < 0) start = index;
3237
+ quote = char;
3238
+ continue;
3239
+ }
3240
+ if (char === "(") {
3241
+ if (start < 0) start = index;
3242
+ depth++;
3243
+ continue;
3244
+ }
3245
+ if (char === ")") {
3246
+ depth--;
3247
+ continue;
3248
+ }
3249
+ if (char === "[") {
3250
+ if (start < 0) start = index;
3251
+ bracketDepth++;
3252
+ continue;
3253
+ }
3254
+ if (char === "]") {
3255
+ bracketDepth--;
3256
+ continue;
3257
+ }
3258
+ if (/\s/.test(char) && depth === 0 && bracketDepth === 0) {
3259
+ if (start >= 0) tokens.push(source.slice(start, index));
3260
+ start = -1;
3261
+ } else if (start < 0) start = index;
3262
+ }
3263
+ if (start >= 0) tokens.push(source.slice(start));
3264
+ return tokens;
3265
+ }
3266
+ function parseString(token) {
3267
+ const quote = token[0];
3268
+ let result = "";
3269
+ for (let index = 1; index < token.length - 1; index++) {
3270
+ const char = token[index];
3271
+ if (char === "\\" && index + 1 < token.length - 1) {
3272
+ const next = token[++index];
3273
+ result += next === quote || next === "\\" ? next : `\\${next}`;
3274
+ } else result += char;
3275
+ }
3276
+ return result;
3277
+ }
3278
+ function parseAtom(token) {
3279
+ if (token.startsWith("(") && token.endsWith(")")) return parseCall(token.slice(1, -1));
3280
+ if (token.startsWith("\"") && token.endsWith("\"") || token.startsWith("'") && token.endsWith("'")) return {
3281
+ kind: "literal",
3282
+ value: parseString(token)
3283
+ };
3284
+ if (token === "true" || token === "false") return {
3285
+ kind: "literal",
3286
+ value: token === "true"
3287
+ };
3288
+ if (token === "null") return {
3289
+ kind: "literal",
3290
+ value: null
3291
+ };
3292
+ if (token === "undefined") return {
3293
+ kind: "literal",
3294
+ value: void 0
3295
+ };
3296
+ if (/^-?(?:\d+\.?\d*|\.\d+)$/.test(token)) return {
3297
+ kind: "literal",
3298
+ value: Number(token)
3299
+ };
3300
+ return {
3301
+ kind: "path",
3302
+ value: token
3303
+ };
3304
+ }
3305
+ function hashSeparator(token) {
3306
+ let quote = "";
3307
+ let depth = 0;
3308
+ for (let index = 0; index < token.length; index++) {
3309
+ const char = token[index];
3310
+ if (quote) {
3311
+ if (char === "\\") index++;
3312
+ else if (char === quote) quote = "";
3313
+ } else if (char === "\"" || char === "'") quote = char;
3314
+ else if (char === "(") depth++;
3315
+ else if (char === ")") depth--;
3316
+ else if (char === "=" && depth === 0) return index;
3317
+ }
3318
+ return -1;
3319
+ }
3320
+ function parseCall(source) {
3321
+ const tokens = tokenizeExpression(source.trim());
3322
+ const name = tokens.shift() ?? "";
3323
+ const args = [];
3324
+ const hash = {};
3325
+ for (const token of tokens) {
3326
+ const separator = hashSeparator(token);
3327
+ if (separator > 0) hash[token.slice(0, separator)] = parseAtom(token.slice(separator + 1));
3328
+ else args.push(parseAtom(token));
3329
+ }
3330
+ return {
3331
+ kind: "call",
3332
+ name,
3333
+ args,
3334
+ hash
3335
+ };
3336
+ }
3337
+ function findTagEnd(source, start, triple) {
3338
+ const close = triple ? "}}}" : "}}";
3339
+ let quote = "";
3340
+ let depth = 0;
3341
+ for (let index = start; index <= source.length - close.length; index++) {
3342
+ const char = source[index];
3343
+ if (quote) {
3344
+ if (char === "\\") index++;
3345
+ else if (char === quote) quote = "";
3346
+ continue;
3347
+ }
3348
+ if (char === "\"" || char === "'") quote = char;
3349
+ else if (char === "(") depth++;
3350
+ else if (char === ")") depth--;
3351
+ else if (depth === 0 && source.startsWith(close, index)) return index;
3352
+ }
3353
+ throw new Error("Parse error: unclosed template expression");
3354
+ }
3355
+ function parseTemplate(source) {
3356
+ const root = [];
3357
+ const stack = [];
3358
+ let target = root;
3359
+ let cursor = 0;
3360
+ while (cursor < source.length) {
3361
+ const open = source.indexOf("{{", cursor);
3362
+ if (open < 0) {
3363
+ if (cursor < source.length) target.push({
3364
+ kind: "text",
3365
+ value: source.slice(cursor)
3366
+ });
3367
+ break;
3368
+ }
3369
+ if (open > cursor) target.push({
3370
+ kind: "text",
3371
+ value: source.slice(cursor, open)
3372
+ });
3373
+ if (source.startsWith("{{!--", open)) {
3374
+ const end = source.indexOf("--}}", open + 6);
3375
+ if (end < 0) throw new Error("Parse error: unclosed comment");
3376
+ cursor = end + 4;
3377
+ continue;
3378
+ }
3379
+ const triple = source.startsWith("{{{", open);
3380
+ const contentStart = open + (triple ? 3 : 2);
3381
+ const end = findTagEnd(source, contentStart, triple);
3382
+ const raw = source.slice(contentStart, end).trim();
3383
+ cursor = end + (triple ? 3 : 2);
3384
+ if (!raw || raw.startsWith("!")) continue;
3385
+ if (raw === "else" || raw.startsWith("else ")) {
3386
+ const current = stack[stack.length - 1];
3387
+ if (!current) throw new Error("Parse error: unexpected else");
3388
+ target = current.node.inverse;
3389
+ if (raw.length > 4) {
3390
+ const parentTarget = target;
3391
+ const nested = {
3392
+ kind: "block",
3393
+ expression: parseCall(raw.slice(5)),
3394
+ body: [],
3395
+ inverse: [],
3396
+ inverted: false
3397
+ };
3398
+ target.push(nested);
3399
+ target = nested.body;
3400
+ stack.push({
3401
+ node: nested,
3402
+ target: parentTarget,
3403
+ inverted: false
3404
+ });
3405
+ }
3406
+ continue;
3407
+ }
3408
+ if (raw.startsWith("#") || raw.startsWith("^")) {
3409
+ const inverted = raw.startsWith("^");
3410
+ const node = {
3411
+ kind: "block",
3412
+ expression: parseCall(raw.slice(1)),
3413
+ body: [],
3414
+ inverse: [],
3415
+ inverted
3416
+ };
3417
+ target.push(node);
3418
+ stack.push({
3419
+ node,
3420
+ target,
3421
+ inverted
3422
+ });
3423
+ target = node.body;
3424
+ continue;
3425
+ }
3426
+ if (raw.startsWith("/")) {
3427
+ const current = stack.pop();
3428
+ if (!current || current.node.expression.name !== raw.slice(1).trim()) throw new Error(`Parse error: mismatched ${raw}`);
3429
+ if (current.inverted) [current.node.body, current.node.inverse] = [current.node.inverse, current.node.body];
3430
+ target = current.target;
3431
+ continue;
3432
+ }
3433
+ if (raw.startsWith(">")) target.push({
3434
+ kind: "partial",
3435
+ expression: parseCall(raw.slice(1))
3436
+ });
3437
+ else target.push({
3438
+ kind: "output",
3439
+ expression: parseCall(raw.startsWith("&") ? raw.slice(1) : raw),
3440
+ escaped: !triple && !raw.startsWith("&")
3441
+ });
3442
+ }
3443
+ if (stack.length) throw new Error(`Parse error: unclosed block ${stack[stack.length - 1].node.expression.name}`);
3444
+ return root;
3445
+ }
3446
+ function pathParts(path) {
3447
+ const parts = [];
3448
+ let start = 0;
3449
+ let bracketStart = -1;
3450
+ for (let index = 0; index <= path.length; index++) {
3451
+ const char = path[index];
3452
+ if (char === "[" && bracketStart < 0) {
3453
+ if (index > start) parts.push(path.slice(start, index).replace(/[./]+$/, ""));
3454
+ bracketStart = index + 1;
3455
+ } else if (char === "]" && bracketStart >= 0) {
3456
+ parts.push(path.slice(bracketStart, index));
3457
+ start = index + 1;
3458
+ bracketStart = -1;
3459
+ } else if ((char === "." || char === "/" || char === void 0) && bracketStart < 0) {
3460
+ if (index > start) parts.push(path.slice(start, index));
3461
+ start = index + 1;
3462
+ }
3463
+ }
3464
+ return parts.filter(Boolean);
3465
+ }
3466
+ function property(parent, key) {
3467
+ if (parent == null) return void 0;
3468
+ if (key === "length" && (typeof parent === "string" || Array.isArray(parent))) return parent.length;
3469
+ if (typeof parent !== "object" && typeof parent !== "function") return void 0;
3470
+ if (key === "__proto__" || key === "prototype" || key === "constructor") return void 0;
3471
+ const record = parent;
3472
+ return Object.hasOwn(record, key) ? record[key] : void 0;
3473
+ }
3474
+ function resolvePath(path, frame) {
3475
+ if (path === "this" || path === ".") return frame.context;
3476
+ let current = frame;
3477
+ while (path.startsWith("../")) {
3478
+ current = current.parents[0] ?? current;
3479
+ path = path.slice(3);
3480
+ }
3481
+ if (path === "this" || path === ".") return current.context;
3482
+ if (path.startsWith("this.")) path = path.slice(5);
3483
+ if (path === "@root") return current.root;
3484
+ if (path.startsWith("@root.")) {
3485
+ let value = current.root;
3486
+ for (const part of pathParts(path.slice(6))) value = property(value, part);
3487
+ return value;
3488
+ }
3489
+ if (path.startsWith("@")) {
3490
+ const parts = pathParts(path.slice(1));
3491
+ let value = current.data[parts.shift() ?? ""];
3492
+ for (const part of parts) value = property(value, part);
3493
+ return value;
3494
+ }
3495
+ let value = current.context;
3496
+ for (const part of pathParts(path)) value = property(value, part);
3497
+ return typeof value === "function" ? value.call(current.context) : value;
3498
+ }
3499
+ function childFrame(frame, context, data = frame.data) {
3500
+ return {
3501
+ context,
3502
+ parents: [frame, ...frame.parents],
3503
+ root: frame.root,
3504
+ data
3505
+ };
3506
+ }
3507
+ function evaluateExpression(expression, frame, evaluation) {
3508
+ if (expression.kind === "literal") return expression.value;
3509
+ if (expression.kind === "path") return resolvePath(expression.value, frame);
3510
+ return evaluateCall(expression, frame, evaluation, false);
3511
+ }
3512
+ function isConditionalTruthy(value, includeZero = false) {
3513
+ return value === 0 ? includeZero : Boolean(value) && !(Array.isArray(value) && value.length === 0);
3514
+ }
3515
+ function renderNodes(nodes, frame, evaluation) {
3516
+ let result = "";
3517
+ for (const node of nodes) if (node.kind === "text") result += node.value;
3518
+ else if (node.kind === "output") {
3519
+ const value = evaluateCall(node.expression, frame, evaluation, false);
3520
+ result += stringify$1(value, node.escaped && !evaluation.options.noEscape);
3521
+ } else if (node.kind === "partial") result += renderPartial(node, frame, evaluation);
3522
+ else result += evaluateBlock(node, frame, evaluation);
3523
+ return result;
3524
+ }
3525
+ function helperOptions(name, hash, frame, evaluation, body = [], inverse = []) {
3526
+ return {
3527
+ name,
3528
+ hash,
3529
+ data: frame.data,
3530
+ fn: (context = frame.context, options) => renderNodes(body, childFrame(frame, context, options?.data ?? frame.data), evaluation),
3531
+ inverse: (context = frame.context, options) => renderNodes(inverse, childFrame(frame, context, options?.data ?? frame.data), evaluation),
3532
+ lookupProperty: property
3533
+ };
3534
+ }
3535
+ function evaluateHash(hash, frame, evaluation) {
3536
+ const result = {};
3537
+ for (const key in hash) result[key] = evaluateExpression(hash[key], frame, evaluation);
3538
+ return result;
3539
+ }
3540
+ function findHelper(name, evaluation) {
3541
+ return evaluation.runtime.helpers?.[name] ?? evaluation.helpers.get(name);
3542
+ }
3543
+ function evaluateCall(call, frame, evaluation, forceHelper, body = [], inverse = []) {
3544
+ const helper = findHelper(call.name, evaluation);
3545
+ const args = call.args.map((argument) => evaluateExpression(argument, frame, evaluation));
3546
+ const hash = evaluateHash(call.hash, frame, evaluation);
3547
+ if (helper) return helper.call(frame.context, ...args, helperOptions(call.name, hash, frame, evaluation, body, inverse));
3548
+ if (call.name === "lookup" && args.length >= 2) return property(args[0], String(args[1]));
3549
+ if (forceHelper || args.length) throw new Error(`Missing helper: "${call.name}"`);
3550
+ for (const _key in hash) throw new Error(`Missing helper: "${call.name}"`);
3551
+ return resolvePath(call.name, frame);
3552
+ }
3553
+ function evaluateBlock(node, frame, evaluation) {
3554
+ const { name } = node.expression;
3555
+ if (findHelper(name, evaluation)) return stringify$1(evaluateCall(node.expression, frame, evaluation, true, node.body, node.inverse), false);
3556
+ const args = node.expression.args.map((argument) => evaluateExpression(argument, frame, evaluation));
3557
+ const value = args.length ? args[0] : resolvePath(name, frame);
3558
+ const hash = evaluateHash(node.expression.hash, frame, evaluation);
3559
+ if (name === "if" || name === "unless") {
3560
+ const truthy = isConditionalTruthy(value, hash.includeZero === true);
3561
+ return renderNodes((name === "if" ? truthy : !truthy) ? node.body : node.inverse, frame, evaluation);
3562
+ }
3563
+ if (name === "each") {
3564
+ if (Array.isArray(value)) {
3565
+ if (!value.length) return renderNodes(node.inverse, frame, evaluation);
3566
+ return value.map((item, index) => renderNodes(node.body, childFrame(frame, item, {
3567
+ ...frame.data,
3568
+ index,
3569
+ key: index,
3570
+ first: index === 0,
3571
+ last: index === value.length - 1
3572
+ }), evaluation)).join("");
3573
+ }
3574
+ if (value && typeof value === "object") {
3575
+ const entries = Object.entries(value);
3576
+ if (!entries.length) return renderNodes(node.inverse, frame, evaluation);
3577
+ return entries.map(([key, item], index) => renderNodes(node.body, childFrame(frame, item, {
3578
+ ...frame.data,
3579
+ index,
3580
+ key,
3581
+ first: index === 0,
3582
+ last: index === entries.length - 1
3583
+ }), evaluation)).join("");
3584
+ }
3585
+ return renderNodes(node.inverse, frame, evaluation);
3586
+ }
3587
+ if (name === "with") return isConditionalTruthy(value) ? renderNodes(node.body, childFrame(frame, value), evaluation) : renderNodes(node.inverse, frame, evaluation);
3588
+ const resolved = resolvePath(name, frame);
3589
+ if (Array.isArray(resolved)) return resolved.map((item) => renderNodes(node.body, childFrame(frame, item), evaluation)).join("") || renderNodes(node.inverse, frame, evaluation);
3590
+ return resolved === false || resolved == null ? renderNodes(node.inverse, frame, evaluation) : renderNodes(node.body, childFrame(frame, resolved), evaluation);
3591
+ }
3592
+ function renderPartial(node, frame, evaluation) {
3593
+ const partial = evaluation.runtime.partials?.[node.expression.name] ?? evaluation.partials.get(node.expression.name);
3594
+ if (partial === void 0) throw new Error(`The partial ${node.expression.name} could not be found`);
3595
+ const context = node.expression.args.length ? evaluateExpression(node.expression.args[0], frame, evaluation) : frame.context;
3596
+ const hash = evaluateHash(node.expression.hash, frame, evaluation);
3597
+ const merged = hash && context && typeof context === "object" ? {
3598
+ ...context,
3599
+ ...hash
3600
+ } : context;
3601
+ return typeof partial === "function" ? partial(merged, evaluation.runtime) : compile$1(partial, evaluation.options)(merged, evaluation.runtime);
3602
+ }
3603
+ function stringify$1(value, shouldEscape) {
3604
+ if (value == null) return "";
3605
+ if (value instanceof SafeString) return value.toString();
3606
+ const text = String(value);
3607
+ return shouldEscape ? escapeExpression(text) : text;
3608
+ }
3609
+ /** Escape a value with Handlebars' HTML entity set. */
3610
+ function escapeExpression(value) {
3611
+ return (value == null ? "" : String(value)).replace(/[&<>"'`=]/g, (char) => ({
3612
+ "&": "&amp;",
3613
+ "<": "&lt;",
3614
+ ">": "&gt;",
3615
+ "\"": "&quot;",
3616
+ "'": "&#x27;",
3617
+ "`": "&#x60;",
3618
+ "=": "&#x3D;"
3619
+ })[char] ?? char);
3620
+ }
3621
+ /** Compile a template into a reusable rendering function. */
3622
+ function compile$1(source, options = {}) {
3623
+ const nodes = parseTemplate(stripStandalone(source));
3624
+ return (context, runtime = {}) => {
3625
+ const root = context ?? {};
3626
+ const frame = {
3627
+ context: root,
3628
+ parents: [],
3629
+ root,
3630
+ data: {
3631
+ root,
3632
+ ...runtime.data ?? {}
3633
+ }
3634
+ };
3635
+ return renderNodes(nodes, frame, {
3636
+ helpers,
3637
+ partials,
3638
+ options,
3639
+ runtime
3640
+ });
3641
+ };
3642
+ }
3643
+ /** Create an isolated template engine with its own helper and partial registries. */
3644
+ function create() {
3645
+ return new TemplateEngine();
3646
+ }
3647
+ /** Isolated template compiler and registry. */
3648
+ var TemplateEngine = class {
3649
+ #helpers = /* @__PURE__ */ new Map();
3650
+ #partials = /* @__PURE__ */ new Map();
3651
+ /** Register a helper in this engine. */
3652
+ registerHelper(name, helper) {
3653
+ this.#helpers.set(name, helper);
3654
+ }
3655
+ /** Register a partial in this engine. */
3656
+ registerPartial(name, partial) {
3657
+ this.#partials.set(name, partial);
3658
+ }
3659
+ /** Compile a template using this engine's registries. */
3660
+ compile(source, options = {}) {
3661
+ const nodes = parseTemplate(stripStandalone(source));
3662
+ return (context, runtime = {}) => {
3663
+ const root = context ?? {};
3664
+ const frame = {
3665
+ context: root,
3666
+ parents: [],
3667
+ root,
3668
+ data: {
3669
+ root,
3670
+ ...runtime.data ?? {}
3671
+ }
3672
+ };
3673
+ return renderNodes(nodes, frame, {
3674
+ helpers: this.#helpers,
3675
+ partials: this.#partials,
3676
+ options,
3677
+ runtime
3678
+ });
3679
+ };
3680
+ }
3681
+ };
3682
+ //#endregion
3683
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/prompt.ts
3684
+ const OPENING_XML = /^<([a-z_-]+)(?:\s+[^>]*)?>$/;
3685
+ /**
3686
+ * Closing XML tag matcher, manual equivalent of `/^<\/([a-z_-]+)>$/` — avoids a
3687
+ * RegExp exec (and match array allocation) per `<`-prefixed line. Caller
3688
+ * guarantees `s` starts `</`.
3689
+ */
3690
+ function closingTagName(s) {
3691
+ const n = s.length;
3692
+ if (n < 4 || s.charCodeAt(n - 1) !== 62) return null;
3693
+ for (let j = 2; j < n - 1; j++) {
3694
+ const c = s.charCodeAt(j);
3695
+ if (!(c >= 97 && c <= 122 || c === 45 || c === 95)) return null;
3696
+ }
3697
+ return s.slice(2, n - 1);
3698
+ }
3699
+ /**
3700
+ * Manual equivalent of {@link OPENING_XML}. Caller guarantees `s` starts with
3701
+ * `<` but not `</`. Falls back to the regex when the char after the tag name
3702
+ * is non-ASCII (possible unicode whitespace).
3703
+ */
3704
+ function openingTagName(s) {
3705
+ const n = s.length;
3706
+ if (n < 3 || s.charCodeAt(n - 1) !== 62) return null;
3707
+ let j = 1;
3708
+ while (j < n - 1) {
3709
+ const c = s.charCodeAt(j);
3710
+ if (c >= 97 && c <= 122 || c === 45 || c === 95) j++;
3711
+ else break;
3712
+ }
3713
+ if (j === 1) return null;
3714
+ if (j === n - 1) return s.slice(1, j);
3715
+ const c = s.charCodeAt(j);
3716
+ if (c !== 32 && c !== 9) {
3717
+ if (c < 128) return null;
3718
+ const match = OPENING_XML.exec(s);
3719
+ return match ? match[1] : null;
3720
+ }
3721
+ return s.indexOf(">", j + 1) === n - 1 ? s.slice(1, j) : null;
3722
+ }
3723
+ const TABLE_ROW = /^\|.*\|$/;
3724
+ const TABLE_SEP = /^\|[-:\s|]+\|$/;
3725
+ const NON_BLANK = /\S/;
3726
+ /**
3727
+ * RFC 2119 keywords (plus project aliases NEVER/AVOID) wrapped in markdown bold
3728
+ * — `**MUST**`, `**MUST NOT**`, `**NEVER**`, etc.
3729
+ */
3730
+ const RFC2119_BOLD = /\*\*(MUST NOT|SHOULD NOT|RECOMMENDED|REQUIRED|OPTIONAL|SHOULD|MUST|MAY|NEVER|AVOID)\*\*/g;
3731
+ /**
3732
+ * Fast pre-check for {@link normalizeRfc2119}: a line that lacks every one of
3733
+ * these substrings is untouched by all three replacements, so the
3734
+ * split/replace/join machinery can be skipped entirely.
3735
+ */
3736
+ const RFC2119_GUARD = /\*\*(?:MUST|SHOULD|RECOMMENDED|REQUIRED|OPTIONAL|MAY|NEVER|AVOID)|MUST NOT|SHOULD NOT/;
3737
+ const MUST_NOT = /\bMUST NOT\b/g;
3738
+ const SHOULD_NOT = /\bSHOULD NOT\b/g;
3739
+ function applyRfc2119(text) {
3740
+ return text.replace(RFC2119_BOLD, "$1").replace(MUST_NOT, "NEVER").replace(SHOULD_NOT, "AVOID");
3741
+ }
3742
+ /**
3743
+ * Normalize RFC 2119 markers per project convention:
3744
+ * - Strip `**KEYWORD**` bold (visual noise, no semantics).
3745
+ * - Alias `MUST NOT` → `NEVER` and `SHOULD NOT` → `AVOID` (single-token equivalents).
3746
+ * Skips spans inside inline code (`` `…` ``) so alias definitions can be quoted literally.
3747
+ */
3748
+ function normalizeRfc2119(line) {
3749
+ if (!RFC2119_GUARD.test(line)) return line;
3750
+ if (!line.includes("`")) return applyRfc2119(line);
3751
+ const segments = line.split("`");
3752
+ for (let i = 0; i < segments.length; i += 2) segments[i] = applyRfc2119(segments[i]);
3753
+ return segments.join("`");
3754
+ }
3755
+ /** Compact a table row by trimming cell padding */
3756
+ function compactTableRow(line) {
3757
+ return line.split("|").map((c) => c.trim()).join("|");
3758
+ }
3759
+ /** Compact a table separator row */
3760
+ function compactTableSep(line) {
3761
+ return `|${line.split("|").filter((c) => c.trim()).map((c) => {
3762
+ const trimmed = c.trim();
3763
+ const left = trimmed.startsWith(":");
3764
+ const right = trimmed.endsWith(":");
3765
+ if (left && right) return ":---:";
3766
+ if (left) return ":---";
3767
+ if (right) return "---:";
3768
+ return "---";
3769
+ }).join("|")}|`;
3770
+ }
3771
+ const HTML_COMMENT_OPEN = "<!--";
3772
+ const HTML_COMMENT_CLOSE = "-->";
3773
+ const ASCII_SYMBOLS = /\.{3}|<->|->|<-|!=|<=|>=/g;
3774
+ const ASCII_SYMBOL_REPLACEMENTS = {
3775
+ "...": "…",
3776
+ "<->": "↔",
3777
+ "->": "→",
3778
+ "<-": "←",
3779
+ "!=": "≠",
3780
+ "<=": "≤",
3781
+ ">=": "≥"
3782
+ };
3783
+ const replaceAsciiSymbol = (match) => ASCII_SYMBOL_REPLACEMENTS[match];
3784
+ function replaceCommonAsciiSymbols(line) {
3785
+ return line.replace(ASCII_SYMBOLS, replaceAsciiSymbol);
3786
+ }
3787
+ function replaceCommonAsciiSymbolsOutsideHtmlComments(line, state) {
3788
+ if (!state.inHtmlComment && !line.includes(HTML_COMMENT_OPEN)) return replaceCommonAsciiSymbols(line);
3789
+ let result = "";
3790
+ let cursor = 0;
3791
+ while (cursor < line.length) {
3792
+ if (state.inHtmlComment) {
3793
+ const closeIndex = line.indexOf(HTML_COMMENT_CLOSE, cursor);
3794
+ if (closeIndex === -1) return result + line.slice(cursor);
3795
+ result += line.slice(cursor, closeIndex + 3);
3796
+ cursor = closeIndex + 3;
3797
+ state.inHtmlComment = false;
3798
+ continue;
3799
+ }
3800
+ const openIndex = line.indexOf(HTML_COMMENT_OPEN, cursor);
3801
+ if (openIndex === -1) {
3802
+ result += replaceCommonAsciiSymbols(line.slice(cursor));
3803
+ return result;
3804
+ }
3805
+ result += replaceCommonAsciiSymbols(line.slice(cursor, openIndex));
3806
+ const closeIndex = line.indexOf(HTML_COMMENT_CLOSE, openIndex + 4);
3807
+ if (closeIndex === -1) {
3808
+ result += line.slice(openIndex);
3809
+ state.inHtmlComment = true;
3810
+ return result;
3811
+ }
3812
+ result += line.slice(openIndex, closeIndex + 3);
3813
+ cursor = closeIndex + 3;
3814
+ }
3815
+ return result;
3816
+ }
3817
+ function format(content, options = {}) {
3818
+ const { renderPhase = "post-render", replaceAsciiSymbols = false, normalizeRfc2119: shouldNormalizeRfc2119 = false } = options;
3819
+ const isPreRender = renderPhase === "pre-render";
3820
+ const lines = content.split("\n");
3821
+ const result = new Array(lines.length);
3822
+ let n = 0;
3823
+ let inCodeBlock = false;
3824
+ const htmlCommentState = { inHtmlComment: false };
3825
+ const topLevelTags = [];
3826
+ for (let i = 0; i < lines.length; i++) {
3827
+ const raw = lines[i];
3828
+ const last = raw.charCodeAt(raw.length - 1);
3829
+ let line = last <= 32 || last >= 128 ? raw.trimEnd() : raw;
3830
+ let s = 0;
3831
+ let first = line.charCodeAt(0);
3832
+ while (first === 32 || first === 9) first = line.charCodeAt(++s);
3833
+ if (first >= 128) {
3834
+ s = line.length - line.trimStart().length;
3835
+ first = line.charCodeAt(s);
3836
+ }
3837
+ if ((first === 96 || first === 126) && (line.startsWith("```", s) || line.startsWith("~~~", s))) {
3838
+ inCodeBlock = !inCodeBlock;
3839
+ result[n++] = line;
3840
+ continue;
3841
+ }
3842
+ if (inCodeBlock) {
3843
+ result[n++] = line;
3844
+ continue;
3845
+ }
3846
+ if (replaceAsciiSymbols) {
3847
+ const replaced = replaceCommonAsciiSymbolsOutsideHtmlComments(line, htmlCommentState);
3848
+ if (replaced !== line) {
3849
+ line = replaced;
3850
+ s = 0;
3851
+ first = line.charCodeAt(0);
3852
+ while (first === 32 || first === 9) first = line.charCodeAt(++s);
3853
+ if (first >= 128) {
3854
+ s = line.length - line.trimStart().length;
3855
+ first = line.charCodeAt(s);
3856
+ }
3857
+ }
3858
+ }
3859
+ let isClosingLine = false;
3860
+ if (first === 60) {
3861
+ const trimmedStart = s === 0 ? line : line.slice(s);
3862
+ if (trimmedStart.charCodeAt(1) === 47) {
3863
+ const tagName = closingTagName(trimmedStart);
3864
+ if (tagName !== null) {
3865
+ isClosingLine = true;
3866
+ if (topLevelTags.length > 0 && topLevelTags[topLevelTags.length - 1] === tagName) topLevelTags.pop();
3867
+ }
3868
+ } else if (s === 0 && !trimmedStart.endsWith("/>")) {
3869
+ const tagName = openingTagName(trimmedStart);
3870
+ if (tagName !== null) topLevelTags.push(tagName);
3871
+ }
3872
+ } else if (first === 124) {
3873
+ const trimmedStart = s === 0 ? line : line.slice(s);
3874
+ if (TABLE_SEP.test(trimmedStart)) line = `${line.slice(0, s)}${compactTableSep(trimmedStart)}`;
3875
+ else if (TABLE_ROW.test(trimmedStart)) line = `${line.slice(0, s)}${compactTableRow(trimmedStart)}`;
3876
+ }
3877
+ if (shouldNormalizeRfc2119) line = normalizeRfc2119(line);
3878
+ if (s >= line.length) {
3879
+ const next = lines[i + 1];
3880
+ if (next === void 0 || next.length === 0 || !NON_BLANK.test(next)) {
3881
+ while (n > 0 && result[n - 1].length === 0) n--;
3882
+ let j = i + 1;
3883
+ while (j < lines.length && (lines[j].length === 0 || !NON_BLANK.test(lines[j]))) j++;
3884
+ i = j - 1;
3885
+ continue;
3886
+ }
3887
+ if (n === 0 || result[n - 1].length === 0) continue;
3888
+ }
3889
+ if (isClosingLine || isPreRender && first === 123 && line.startsWith("{{/", s)) while (n > 0 && result[n - 1].length === 0) n--;
3890
+ result[n++] = line;
3891
+ }
3892
+ while (n > 0 && result[n - 1].length === 0) n--;
3893
+ result.length = n;
3894
+ return result.join("\n");
3895
+ }
3896
+ const handlebars = create();
3897
+ handlebars.registerHelper("arg", function(index) {
3898
+ const args = this.args ?? [];
3899
+ const parsedIndex = typeof index === "number" ? index : Number.parseInt(index, 10);
3900
+ if (!Number.isFinite(parsedIndex)) return "";
3901
+ const zeroBased = parsedIndex - 1;
3902
+ if (zeroBased < 0) return "";
3903
+ return args[zeroBased] ?? "";
3904
+ });
3905
+ /**
3906
+ * {{#list items prefix="- " suffix="" join="\n"}}{{this}}{{/list}}
3907
+ * Renders an array with customizable prefix, suffix, and join separator.
3908
+ * Note: Use \n in join for newlines (will be unescaped automatically).
3909
+ */
3910
+ handlebars.registerHelper("list", function(context, options) {
3911
+ if (!Array.isArray(context) || context.length === 0) return "";
3912
+ const prefix = options.hash.prefix ?? "";
3913
+ const suffix = options.hash.suffix ?? "";
3914
+ const separator = (options.hash.join ?? "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ");
3915
+ return context.map((item) => `${prefix}${options.fn(item)}${suffix}`).join(separator);
3916
+ });
3917
+ /**
3918
+ * {{join array ", "}}
3919
+ * Joins an array with a separator (default: ", ").
3920
+ * Note: Use \n/\t in the separator for newlines/tabs (unescaped automatically,
3921
+ * same convention as {{#list}} — Handlebars string literals carry no escapes).
3922
+ */
3923
+ handlebars.registerHelper("join", (context, separator) => {
3924
+ if (!Array.isArray(context)) return "";
3925
+ const sep = typeof separator === "string" ? separator.replace(/\\n/g, "\n").replace(/\\t/g, " ") : ", ";
3926
+ return context.join(sep);
3927
+ });
3928
+ /**
3929
+ * {{default value "fallback"}}
3930
+ * Returns the value if truthy, otherwise returns the fallback.
3931
+ */
3932
+ handlebars.registerHelper("default", (value, defaultValue) => value || defaultValue);
3933
+ /**
3934
+ * {{pluralize count "item" "items"}}
3935
+ * Returns "1 item" or "5 items" based on count.
3936
+ */
3937
+ handlebars.registerHelper("pluralize", (count, singular, plural) => `${count} ${count === 1 ? singular : plural}`);
3938
+ /**
3939
+ * {{#when value "==" compare}}...{{else}}...{{/when}}
3940
+ * Conditional block with comparison operators: ==, ===, !=, !==, >, <, >=, <=
3941
+ */
3942
+ handlebars.registerHelper("when", function(lhs, operator, rhs, options) {
3943
+ const fn = {
3944
+ "==": (a, b) => a === b,
3945
+ "===": (a, b) => a === b,
3946
+ "!=": (a, b) => a !== b,
3947
+ "!==": (a, b) => a !== b,
3948
+ ">": (a, b) => a > b,
3949
+ "<": (a, b) => a < b,
3950
+ ">=": (a, b) => a >= b,
3951
+ "<=": (a, b) => a <= b
3952
+ }[operator];
3953
+ if (!fn) return options.inverse(this);
3954
+ return fn(lhs, rhs) ? options.fn(this) : options.inverse(this);
3955
+ });
3956
+ /**
3957
+ * {{#ifAny a b c}}...{{else}}...{{/ifAny}}
3958
+ * True if any argument is truthy.
3959
+ */
3960
+ handlebars.registerHelper("ifAny", function(...args) {
3961
+ const options = args.pop();
3962
+ return args.some(Boolean) ? options.fn(this) : options.inverse(this);
3963
+ });
3964
+ /**
3965
+ * {{#ifAll a b c}}...{{else}}...{{/ifAll}}
3966
+ * True if all arguments are truthy.
3967
+ */
3968
+ handlebars.registerHelper("ifAll", function(...args) {
3969
+ const options = args.pop();
3970
+ return args.every(Boolean) ? options.fn(this) : options.inverse(this);
3971
+ });
3972
+ /**
3973
+ * {{#table rows headers="Col1|Col2"}}{{col1}}|{{col2}}{{/table}}
3974
+ * Generates a markdown table from an array of objects.
3975
+ */
3976
+ handlebars.registerHelper("table", function(context, options) {
3977
+ if (!Array.isArray(context) || context.length === 0) return "";
3978
+ const headers = options.hash.headers?.split("|") ?? [];
3979
+ const separator = headers.map(() => "---").join(" | ");
3980
+ return (headers.length > 0 ? `| ${headers.join(" | ")} |\n| ${separator} |\n` : "") + context.map((item) => `| ${options.fn(item).trim()} |`).join("\n");
3981
+ });
3982
+ /**
3983
+ * {{#codeblock lang="diff"}}...{{/codeblock}}
3984
+ * Wraps content in a fenced code block.
3985
+ */
3986
+ handlebars.registerHelper("codeblock", function(options) {
3987
+ return `\`\`\`${options.hash.lang ?? ""}\n${options.fn(this).trim()}\n\`\`\``;
3988
+ });
3989
+ /**
3990
+ * {{#xml "tag"}}content{{/xml}}
3991
+ * Wraps content in XML-style tags. Returns empty string if content is empty.
3992
+ */
3993
+ handlebars.registerHelper("xml", function(tag, options) {
3994
+ const content = options.fn(this).trim();
3995
+ if (!content) return "";
3996
+ return `<${tag}>\n${content}\n</${tag}>`;
3997
+ });
3998
+ /**
3999
+ * {{escapeXml value}}
4000
+ * Escapes XML special characters: & < > "
4001
+ */
4002
+ handlebars.registerHelper("escapeXml", (value) => {
4003
+ if (value == null) return "";
4004
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
4005
+ });
4006
+ /**
4007
+ * {{len array}}
4008
+ * Returns the length of an array or string.
4009
+ */
4010
+ handlebars.registerHelper("len", (value) => {
4011
+ if (Array.isArray(value)) return value.length;
4012
+ if (typeof value === "string") return value.length;
4013
+ return 0;
4014
+ });
4015
+ /**
4016
+ * {{add a b}}
4017
+ * Adds two numbers.
4018
+ */
4019
+ handlebars.registerHelper("add", (a, b) => (a ?? 0) + (b ?? 0));
4020
+ /**
4021
+ * {{sub a b}}
4022
+ * Subtracts b from a.
4023
+ */
4024
+ handlebars.registerHelper("sub", (a, b) => (a ?? 0) - (b ?? 0));
4025
+ /**
4026
+ * {{#has collection item}}...{{else}}...{{/has}}
4027
+ * Checks if an array includes an item or if a Set/Map has a key.
4028
+ */
4029
+ handlebars.registerHelper("has", function(collection, item, options) {
4030
+ let found = false;
4031
+ if (Array.isArray(collection)) found = collection.includes(item);
4032
+ else if (collection instanceof Set) found = collection.has(item);
4033
+ else if (collection instanceof Map) found = collection.has(item);
4034
+ else if (collection && typeof collection === "object") {
4035
+ if (typeof item === "string" || typeof item === "number" || typeof item === "symbol") found = item in collection;
4036
+ }
4037
+ return found ? options.fn(this) : options.inverse(this);
4038
+ });
4039
+ /**
4040
+ * {{includes array item}}
4041
+ * Returns true if array includes item. For use in other helpers.
4042
+ */
4043
+ handlebars.registerHelper("includes", (collection, item) => {
4044
+ if (Array.isArray(collection)) return collection.includes(item);
4045
+ if (collection instanceof Set) return collection.has(item);
4046
+ if (collection instanceof Map) return collection.has(item);
4047
+ return false;
4048
+ });
4049
+ /**
4050
+ * {{not value}}
4051
+ * Returns logical NOT of value. For use in subexpressions.
4052
+ */
4053
+ handlebars.registerHelper("not", (value) => !value);
4054
+ handlebars.registerHelper("jsonStringify", (value) => JSON.stringify(value));
4055
+ const compiledTemplateCache = /* @__PURE__ */ new Map();
4056
+ function compile(template) {
4057
+ const cached = compiledTemplateCache.get(template);
4058
+ if (cached) return cached;
4059
+ const compiled = handlebars.compile(template, {
4060
+ noEscape: true,
4061
+ strict: false
4062
+ });
4063
+ compiledTemplateCache.set(template, compiled);
4064
+ return compiled;
4065
+ }
4066
+ function render(template, context = {}) {
4067
+ return format(compile(template)(context ?? {}), { renderPhase: "post-render" });
4068
+ }
4069
+ //#endregion
4070
+ //#region \0@oxc-project+runtime@0.144.0/helpers/esm/usingCtx.js
4071
+ function _usingCtx() {
4072
+ var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
4073
+ var n = Error();
4074
+ return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
4075
+ }, e = {}, n = [];
4076
+ function using(r, e) {
4077
+ if (null != e) {
4078
+ if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
4079
+ if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
4080
+ if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
4081
+ if ("function" != typeof o) throw new TypeError("Object is not disposable.");
4082
+ t && (o = function o() {
4083
+ try {
4084
+ t.call(e);
4085
+ } catch (r) {
4086
+ return Promise.reject(r);
4087
+ }
4088
+ }), n.push({
4089
+ v: e,
4090
+ d: o,
4091
+ a: r
4092
+ });
4093
+ } else r && n.push({
4094
+ d: e,
4095
+ a: r
4096
+ });
4097
+ return e;
4098
+ }
4099
+ return {
4100
+ e,
4101
+ u: using.bind(null, !1),
4102
+ a: using.bind(null, !0),
4103
+ d: function d() {
4104
+ var o, t = this.e, s = 0;
4105
+ function next() {
4106
+ for (; o = n.pop();) try {
4107
+ if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
4108
+ if (o.d) {
4109
+ var r = o.d.call(o.v);
4110
+ if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
4111
+ } else s |= 1;
4112
+ } catch (r) {
4113
+ return err(r);
4114
+ }
4115
+ if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
4116
+ if (t !== e) throw t;
4117
+ }
4118
+ function err(n) {
4119
+ return t = t !== e ? new r(n, t) : n, next();
4120
+ }
4121
+ return next();
4122
+ }
4123
+ };
4124
+ }
4125
+ //#endregion
4126
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/ptree.ts
4127
+ /**
4128
+ * Process tree management utilities for Bun subprocesses.
4129
+ *
4130
+ * - Track managed child processes for cleanup on shutdown (postmortem).
4131
+ * - Drain stdout/stderr to avoid subprocess pipe deadlocks.
4132
+ * - Cross-platform tree kill for process groups (Windows taskkill, Unix -pid).
4133
+ * - Convenience helpers: captureText / execText, AbortSignal, timeouts.
4134
+ */
4135
+ /**
4136
+ * Base for all exceptions representing child process nonzero exit, killed, or
4137
+ * cancellation.
4138
+ */
4139
+ var Exception = class extends Error {
4140
+ exitCode;
4141
+ stderr;
4142
+ constructor(message, exitCode, stderr) {
4143
+ super(message);
4144
+ this.exitCode = exitCode;
4145
+ this.stderr = stderr;
4146
+ this.name = this.constructor.name;
4147
+ }
4148
+ };
4149
+ /** Exception for nonzero exit codes (not cancellation). */
4150
+ var NonZeroExitError = class extends Exception {
4151
+ static MAX_TRACE = 32768;
4152
+ constructor(exitCode, stderr) {
4153
+ super(`Process exited with code ${exitCode}:\n${stderr}`, exitCode, stderr);
4154
+ }
4155
+ get aborted() {
4156
+ return false;
4157
+ }
4158
+ };
4159
+ /** Exception for explicit process abortion (via signal). */
4160
+ var AbortError = class extends Exception {
4161
+ reason;
4162
+ constructor(reason, stderr) {
4163
+ const msg = reason instanceof Error ? reason.message : String(reason ?? "aborted");
4164
+ super(`Operation cancelled: ${msg}`, -1, stderr);
4165
+ this.reason = reason;
4166
+ }
4167
+ get aborted() {
4168
+ return true;
4169
+ }
4170
+ };
4171
+ /** Exception for process timeout. */
4172
+ var TimeoutError = class extends AbortError {
4173
+ constructor(timeout, stderr) {
4174
+ super(/* @__PURE__ */ new Error(`Timed out after ${Math.round(timeout / 1e3)}s`), stderr);
4175
+ }
4176
+ };
4177
+ /**
4178
+ * ChildProcess wraps a managed subprocess, capturing stderr tail, providing
4179
+ * cross-platform kill/detach logic plus AbortSignal integration.
4180
+ *
4181
+ * Stdout is exposed directly from the underlying Bun subprocess; consumers
4182
+ * must read it (via text(), wait(), etc.) to prevent pipe deadlock.
4183
+ * Stderr is eagerly drained into an internal buffer.
4184
+ */
4185
+ var ChildProcess = class {
4186
+ proc;
4187
+ exposeStderr;
4188
+ #nothrow = false;
4189
+ #stderrTail = "";
4190
+ #stderrChunks;
4191
+ #exitReason;
4192
+ #exitReasonPending;
4193
+ #stderrDone;
4194
+ #exited;
4195
+ #stderrStream;
4196
+ constructor(proc, exposeStderr, retainFullStderr = exposeStderr) {
4197
+ this.proc = proc;
4198
+ this.exposeStderr = exposeStderr;
4199
+ if (retainFullStderr) this.#stderrChunks = [];
4200
+ const dec = new TextDecoder();
4201
+ const trim = () => {
4202
+ if (this.#stderrTail.length > NonZeroExitError.MAX_TRACE) this.#stderrTail = this.#stderrTail.slice(-NonZeroExitError.MAX_TRACE);
4203
+ };
4204
+ let stderrStream = proc.stderr;
4205
+ if (exposeStderr) {
4206
+ const [teeStream, drainStream] = stderrStream.tee();
4207
+ this.#stderrStream = teeStream;
4208
+ stderrStream = drainStream;
4209
+ }
4210
+ this.#stderrDone = (async () => {
4211
+ try {
4212
+ for await (const chunk of stderrStream) {
4213
+ this.#stderrChunks?.push(chunk);
4214
+ this.#stderrTail += dec.decode(chunk, { stream: true });
4215
+ trim();
4216
+ }
4217
+ } catch {}
4218
+ this.#stderrTail += dec.decode();
4219
+ trim();
4220
+ })();
4221
+ const { promise, resolve, reject } = Promise.withResolvers();
4222
+ this.#exited = promise;
4223
+ proc.exited.catch(() => null).then(async (exitCode) => {
4224
+ if (this.#exitReasonPending) {
4225
+ this.#exitReason = this.#exitReasonPending;
4226
+ reject(this.#exitReasonPending);
4227
+ return;
4228
+ }
4229
+ if (exitCode === 0) {
4230
+ resolve(0);
4231
+ return;
4232
+ }
4233
+ await this.#stderrDone;
4234
+ if (exitCode !== null) {
4235
+ this.#exitReason = new NonZeroExitError(exitCode, this.#stderrTail);
4236
+ resolve(exitCode);
4237
+ return;
4238
+ }
4239
+ const ex = this.proc.killed ? new AbortError(/* @__PURE__ */ new Error("process killed"), this.#stderrTail) : new NonZeroExitError(-1, this.#stderrTail);
4240
+ this.#exitReason = ex;
4241
+ reject(ex);
4242
+ });
4243
+ }
4244
+ get pid() {
4245
+ return this.proc.pid;
4246
+ }
4247
+ get exited() {
4248
+ return this.#exited;
4249
+ }
4250
+ get exitCode() {
4251
+ return this.proc.exitCode;
4252
+ }
4253
+ get exitReason() {
4254
+ return this.#exitReason;
4255
+ }
4256
+ get killed() {
4257
+ return this.proc.killed;
4258
+ }
4259
+ get stdin() {
4260
+ return this.proc.stdin;
4261
+ }
4262
+ /** Raw stdout stream. Must be consumed to prevent pipe deadlock. */
4263
+ get stdout() {
4264
+ return this.proc.stdout;
4265
+ }
4266
+ /** Optional stderr stream (only when requested in spawn options). */
4267
+ get stderr() {
4268
+ return this.#stderrStream;
4269
+ }
4270
+ get exitedCleanly() {
4271
+ if (this.#nothrow) return this.#exited;
4272
+ return this.#exited.then((code) => {
4273
+ if (code !== 0) throw new NonZeroExitError(code, this.#stderrTail);
4274
+ return code;
4275
+ });
4276
+ }
4277
+ /** Returns the truncated stderr tail (last 32KB). */
4278
+ peekStderr() {
4279
+ return this.#stderrTail;
4280
+ }
4281
+ nothrow() {
4282
+ this.#nothrow = true;
4283
+ return this;
4284
+ }
4285
+ kill(reason, gracefulMs) {
4286
+ if (reason && !this.#exitReasonPending) this.#exitReasonPending = reason;
4287
+ if (!this.proc.killed) Process.fromPid(this.proc.pid)?.terminate(gracefulMs === void 0 ? void 0 : { gracefulMs })?.catch((e) => void 0);
4288
+ }
4289
+ async text() {
4290
+ const p = new Response(this.stdout).text();
4291
+ if (this.#nothrow) return p;
4292
+ const [text] = await Promise.all([p, this.exitedCleanly]);
4293
+ return text;
4294
+ }
4295
+ async blob() {
4296
+ const p = new Response(this.stdout).blob();
4297
+ if (this.#nothrow) return p;
4298
+ const [blob] = await Promise.all([p, this.exitedCleanly]);
4299
+ return blob;
4300
+ }
4301
+ async json() {
4302
+ return new Response(this.stdout).json();
4303
+ }
4304
+ async arrayBuffer() {
4305
+ return new Response(this.stdout).arrayBuffer();
4306
+ }
4307
+ async bytes() {
4308
+ const body = await new Response(this.stdout).bytes();
4309
+ return body instanceof Uint8Array ? body : new Uint8Array(body);
4310
+ }
4311
+ async wait(opts) {
4312
+ const { allowNonZero = false, allowAbort = false, stderr: stderrMode = "buffer" } = opts ?? {};
4313
+ const stderrChunks = this.#stderrChunks;
4314
+ if (stderrMode === "full" && !stderrChunks) throw new Error("Full stderr capture must be requested when spawning the process (pass stderr: \"full\")");
4315
+ const stdoutP = new Response(this.stdout).text();
4316
+ const stderrP = stderrMode === "full" && stderrChunks ? this.#stderrDone.then(() => new TextDecoder().decode(Buffer.concat(stderrChunks))) : this.#stderrDone.then(() => this.#stderrTail);
4317
+ const [stdout, stderr] = await Promise.all([stdoutP, stderrP]);
4318
+ let exitError;
4319
+ try {
4320
+ await this.#exited;
4321
+ } catch (err) {
4322
+ if (err instanceof Exception) exitError = err;
4323
+ else throw err;
4324
+ }
4325
+ if (!exitError) exitError = this.exitReason;
4326
+ if (!exitError && this.exitCode !== null && this.exitCode !== 0) exitError = new NonZeroExitError(this.exitCode, this.#stderrTail);
4327
+ const exitCode = this.exitCode ?? (exitError && !exitError.aborted ? exitError.exitCode : null);
4328
+ const ok = exitCode === 0;
4329
+ if (exitError) {
4330
+ if (exitError.aborted && !allowAbort || !exitError.aborted && !allowNonZero) throw exitError;
4331
+ }
4332
+ return {
4333
+ stdout,
4334
+ stderr,
4335
+ exitCode,
4336
+ ok,
4337
+ exitError
4338
+ };
4339
+ }
4340
+ attachSignal(signal) {
4341
+ const onAbort = () => this.kill(new AbortError(signal.reason, "<cancelled>"));
4342
+ if (signal.aborted) return void onAbort();
4343
+ signal.addEventListener("abort", onAbort, { once: true });
4344
+ this.#exited.catch(() => {}).finally(() => signal.removeEventListener("abort", onAbort));
4345
+ }
4346
+ attachTimeout(ms) {
4347
+ if (ms <= 0 || this.proc.killed) return;
4348
+ this.#exited.catch(() => {});
4349
+ Promise.race([Bun.sleep(ms).then(() => true), this.proc.exited.then(() => false, () => false)]).then((timedOut) => {
4350
+ if (timedOut) this.kill(new TimeoutError(ms, this.#stderrTail));
4351
+ });
4352
+ }
4353
+ [Symbol.dispose]() {
4354
+ if (this.proc.exitCode !== null) return;
4355
+ this.kill(new AbortError("process disposed", this.#stderrTail));
4356
+ }
4357
+ };
4358
+ function spawnInternal(cmd, opts, retainFullStderr) {
4359
+ const { timeout = -1, signal, stderr, ...rest } = opts ?? {};
4360
+ const cp = new ChildProcess(Bun.spawn(cmd, {
4361
+ stdin: "ignore",
4362
+ stdout: "pipe",
4363
+ stderr: "pipe",
4364
+ windowsHide: true,
4365
+ ...rest
4366
+ }), stderr === "full", retainFullStderr);
4367
+ if (signal) cp.attachSignal(signal);
4368
+ if (timeout > 0) cp.attachTimeout(timeout);
4369
+ return cp;
4370
+ }
4371
+ /** Spawn, wait, and return captured output. */
4372
+ async function exec(cmd, opts) {
4373
+ try {
4374
+ var _usingCtx$1 = _usingCtx();
4375
+ const { input, stderr, allowAbort, allowNonZero, ...spawnOpts } = opts ?? {};
4376
+ const stdin = typeof input === "string" ? Buffer.from(input) : input;
4377
+ const resolved = stdin === void 0 ? spawnOpts : {
4378
+ ...spawnOpts,
4379
+ stdin
4380
+ };
4381
+ return await _usingCtx$1.u(spawnInternal(cmd, resolved, stderr === "full")).wait({
4382
+ stderr,
4383
+ allowAbort,
4384
+ allowNonZero
4385
+ });
4386
+ } catch (_) {
4387
+ _usingCtx$1.e = _;
4388
+ } finally {
4389
+ _usingCtx$1.d();
4390
+ }
4391
+ }
4392
+ /** Combine AbortSignals and timeout values into a single signal. */
4393
+ function combineSignals(...signals) {
4394
+ let timeout;
4395
+ let n = 0;
4396
+ for (let i = 0; i < signals.length; i++) {
4397
+ const s = signals[i];
4398
+ if (s instanceof AbortSignal) {
4399
+ if (s.aborted) return s;
4400
+ if (i !== n) signals[n] = s;
4401
+ n++;
4402
+ } else if (typeof s === "number" && s > 0) timeout = timeout === void 0 ? s : Math.min(timeout, s);
4403
+ }
4404
+ if (timeout !== void 0) {
4405
+ signals[n] = AbortSignal.timeout(timeout);
4406
+ n++;
4407
+ }
4408
+ switch (n) {
4409
+ case 0: return;
4410
+ case 1: return signals[0];
4411
+ default: return AbortSignal.any(signals.slice(0, n));
4412
+ }
4413
+ }
4414
+ //#endregion
4415
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/snowflake.ts
4416
+ function randu32() {
4417
+ return crypto.getRandomValues(/* @__PURE__ */ new Uint32Array(1))[0];
4418
+ }
4419
+ const EPOCH = 14200704e5;
4420
+ const MAX_SEQ = 4194303;
4421
+ let Snowflake;
4422
+ (function(_Snowflake) {
4423
+ const PATTERN = _Snowflake.PATTERN = /^[0-9a-f]{16}$/;
4424
+ _Snowflake.EPOCH_TIMESTAMP = EPOCH;
4425
+ _Snowflake.MAX_SEQUENCE = MAX_SEQ;
4426
+ function formatParts(dt, seq) {
4427
+ return (BigInt(dt) << 22n | BigInt(seq)).toString(16).padStart(16, "0");
4428
+ }
4429
+ _Snowflake.formatParts = formatParts;
4430
+ class Source {
4431
+ #seq = 0;
4432
+ constructor(sequence = randu32() & MAX_SEQ) {
4433
+ this.#seq = sequence & MAX_SEQ;
4434
+ }
4435
+ get sequence() {
4436
+ return this.#seq & MAX_SEQ;
4437
+ }
4438
+ set sequence(v) {
4439
+ this.#seq = v & MAX_SEQ;
4440
+ }
4441
+ reset() {
4442
+ this.#seq = 0;
4443
+ }
4444
+ generate(timestamp) {
4445
+ const seq = this.#seq + 1 & MAX_SEQ;
4446
+ const dt = timestamp - EPOCH;
4447
+ this.#seq = seq;
4448
+ return formatParts(dt, seq);
4449
+ }
4450
+ }
4451
+ _Snowflake.Source = Source;
4452
+ let defaultSource;
4453
+ function next(timestamp = Date.now()) {
4454
+ defaultSource ??= new Source();
4455
+ return defaultSource.generate(timestamp);
4456
+ }
4457
+ _Snowflake.next = next;
4458
+ function valid(value) {
4459
+ return value.length === 16 && PATTERN.test(value);
4460
+ }
4461
+ _Snowflake.valid = valid;
4462
+ function lowerbound(timelike) {
4463
+ switch (typeof timelike) {
4464
+ case "object": return formatParts(timelike.getTime() - EPOCH, 0);
4465
+ case "number": return formatParts(timelike - EPOCH, 0);
4466
+ case "string": return timelike;
4467
+ }
4468
+ }
4469
+ _Snowflake.lowerbound = lowerbound;
4470
+ function upperbound(timelike) {
4471
+ switch (typeof timelike) {
4472
+ case "object": return formatParts(timelike.getTime() - EPOCH, MAX_SEQ);
4473
+ case "number": return formatParts(timelike - EPOCH, MAX_SEQ);
4474
+ case "string": return timelike;
4475
+ }
4476
+ }
4477
+ _Snowflake.upperbound = upperbound;
4478
+ function getSequence(value) {
4479
+ return Number.parseInt(value.substring(8, 16), 16) & MAX_SEQ;
4480
+ }
4481
+ _Snowflake.getSequence = getSequence;
4482
+ function getTimestamp(value) {
4483
+ const hi = Number.parseInt(value.substring(0, 8), 16);
4484
+ const lo = Number.parseInt(value.substring(8, 16), 16);
4485
+ return hi * 1024 + (lo >>> 22) + EPOCH;
4486
+ }
4487
+ _Snowflake.getTimestamp = getTimestamp;
4488
+ function getDate(value) {
4489
+ return new Date(getTimestamp(value));
4490
+ }
4491
+ _Snowflake.getDate = getDate;
4492
+ })(Snowflake || (Snowflake = {}));
4493
+ //#endregion
4494
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/stream.ts
4495
+ const trailingEvents = /* @__PURE__ */ new WeakSet();
4496
+ const LF = 10;
4497
+ function parseJsonlChunkCompat(input, beg, end) {
4498
+ if (typeof input === "string") {
4499
+ const { values, error, read, done } = Bun.JSONL.parseChunk(input);
4500
+ return {
4501
+ values,
4502
+ error,
4503
+ read,
4504
+ done
4505
+ };
4506
+ }
4507
+ const start = beg ?? 0;
4508
+ const stop = end ?? input.length;
4509
+ const { values, error, read, done } = Bun.JSONL.parseChunk(input, start, stop);
4510
+ return {
4511
+ values,
4512
+ error,
4513
+ read,
4514
+ done
4515
+ };
4516
+ }
4517
+ async function* readJsonl(stream, signal) {
4518
+ const buffer = new ConcatSink();
4519
+ const source = abortableSource(stream, signal);
4520
+ try {
4521
+ for await (const chunk of source) yield* buffer.pullJSONL(chunk, 0, chunk.length);
4522
+ if (!buffer.isEmpty) {
4523
+ const tail = buffer.flush();
4524
+ if (tail) {
4525
+ buffer.clear();
4526
+ const { values, error, done } = parseJsonlChunkCompat(tail, 0, tail.length);
4527
+ if (values.length > 0) yield* values;
4528
+ if (error) throw error;
4529
+ if (!done) throw new Error("JSONL stream ended unexpectedly");
4530
+ }
4531
+ }
4532
+ } catch (err) {
4533
+ if (signal?.aborted) return;
4534
+ throw err;
4535
+ }
4536
+ }
4537
+ var ConcatSink = class {
4538
+ #space;
4539
+ #length = 0;
4540
+ #ensureCapacity(size) {
4541
+ const space = this.#space;
4542
+ if (space && space.length >= size) return space;
4543
+ const nextSize = space ? Math.max(size, space.length * 2) : size;
4544
+ const next = Buffer.allocUnsafe(nextSize);
4545
+ if (space && this.#length > 0) space.copy(next, 0, 0, this.#length);
4546
+ this.#space = next;
4547
+ return next;
4548
+ }
4549
+ append(chunk) {
4550
+ const n = chunk.length;
4551
+ if (!n) return;
4552
+ const offset = this.#length;
4553
+ this.#ensureCapacity(offset + n).set(chunk, offset);
4554
+ this.#length += n;
4555
+ }
4556
+ reset(chunk) {
4557
+ const n = chunk.length;
4558
+ if (!n) {
4559
+ this.#length = 0;
4560
+ return;
4561
+ }
4562
+ this.#ensureCapacity(n).set(chunk, 0);
4563
+ this.#length = n;
4564
+ }
4565
+ get isEmpty() {
4566
+ return this.#length === 0;
4567
+ }
4568
+ flush() {
4569
+ if (!this.#length) return void 0;
4570
+ return this.#space.subarray(0, this.#length);
4571
+ }
4572
+ clear() {
4573
+ this.#length = 0;
4574
+ }
4575
+ *appendAndFlushLines(chunk) {
4576
+ let pos = 0;
4577
+ while (pos < chunk.length) {
4578
+ const nl = chunk.indexOf(LF, pos);
4579
+ if (nl === -1) {
4580
+ this.append(chunk.subarray(pos));
4581
+ return;
4582
+ }
4583
+ const suffix = chunk.subarray(pos, nl);
4584
+ pos = nl + 1;
4585
+ if (this.isEmpty) yield suffix;
4586
+ else {
4587
+ this.append(suffix);
4588
+ const payload = this.flush();
4589
+ if (payload) {
4590
+ yield payload;
4591
+ this.clear();
4592
+ }
4593
+ }
4594
+ }
4595
+ }
4596
+ appendAndFlushText(chunk, decoder) {
4597
+ const lastNewline = chunk.lastIndexOf(LF);
4598
+ if (lastNewline === -1) {
4599
+ this.append(chunk);
4600
+ return;
4601
+ }
4602
+ const completeEnd = lastNewline + 1;
4603
+ let text;
4604
+ if (this.isEmpty) {
4605
+ const complete = completeEnd === chunk.length ? chunk : chunk.subarray(0, completeEnd);
4606
+ text = decoder.decode(complete);
4607
+ } else {
4608
+ this.append(completeEnd === chunk.length ? chunk : chunk.subarray(0, completeEnd));
4609
+ text = decoder.decode(this.flush());
4610
+ this.clear();
4611
+ }
4612
+ if (completeEnd < chunk.length) this.append(chunk.subarray(completeEnd));
4613
+ return text;
4614
+ }
4615
+ *pullJSONL(chunk, beg, end) {
4616
+ if (this.isEmpty) {
4617
+ const { values, error, read, done } = parseJsonlChunkCompat(chunk, beg, end);
4618
+ if (values.length > 0) yield* values;
4619
+ if (error) throw error;
4620
+ if (done) return;
4621
+ this.reset(chunk.subarray(read, end));
4622
+ return;
4623
+ }
4624
+ const offset = this.#length;
4625
+ const total = offset + (end - beg);
4626
+ const space = this.#ensureCapacity(total);
4627
+ space.set(chunk.subarray(beg, end), offset);
4628
+ this.#length = total;
4629
+ const { values, error, read, done } = parseJsonlChunkCompat(space.subarray(0, total), 0, total);
4630
+ if (values.length > 0) yield* values;
4631
+ if (error) throw error;
4632
+ if (done) {
4633
+ this.#length = 0;
4634
+ return;
4635
+ }
4636
+ const rem = total - read;
4637
+ if (rem < total) space.copyWithin(0, read, total);
4638
+ this.#length = rem;
4639
+ }
4640
+ };
4641
+ function notifySseEventObserver(observer, event) {
4642
+ if (!observer) return;
4643
+ try {
4644
+ observer(event);
4645
+ } catch {}
4646
+ }
4647
+ function isRecoverableTrailingJson(data) {
4648
+ const first = data.trimStart()[0];
4649
+ if (first !== "{" && first !== "[") return false;
4650
+ const recovered = parseStreamingJson(data);
4651
+ return typeof recovered === "object" && recovered !== null;
4652
+ }
4653
+ async function* readSseJson(stream, signal, onEvent) {
4654
+ for await (const sse of readSseEvents(stream, signal)) {
4655
+ const isTrailing = trailingEvents.has(sse);
4656
+ notifySseEventObserver(onEvent, sse);
4657
+ const data = sse.data;
4658
+ if (data === "" || data === "[DONE]") {
4659
+ if (data === "[DONE]") return;
4660
+ continue;
4661
+ }
4662
+ try {
4663
+ yield JSON.parse(data);
4664
+ } catch (err) {
4665
+ if (err instanceof SyntaxError && isTrailing && isRecoverableTrailingJson(data)) return;
4666
+ throw err;
4667
+ }
4668
+ }
4669
+ }
4670
+ const SSE_DECODER = new TextDecoder("utf-8");
4671
+ function flushSseEvent(state) {
4672
+ if (state.event === null && state.data === null && state.id === void 0 && state.retry === void 0) {
4673
+ state.raw = [];
4674
+ return null;
4675
+ }
4676
+ const event = {
4677
+ event: state.event,
4678
+ data: state.data ?? "",
4679
+ raw: state.raw
4680
+ };
4681
+ if (state.id !== void 0) event.id = state.id;
4682
+ if (state.retry !== void 0) event.retry = state.retry;
4683
+ state.event = null;
4684
+ state.data = null;
4685
+ state.raw = [];
4686
+ state.id = void 0;
4687
+ state.retry = void 0;
4688
+ return event;
4689
+ }
4690
+ function pushSseLine(line, state) {
4691
+ if (line.charCodeAt(line.length - 1) === 13) line = line.slice(0, -1);
4692
+ if (line.length === 0) return flushSseEvent(state);
4693
+ if (line.charCodeAt(0) === 58) {
4694
+ state.raw.push(line);
4695
+ return null;
4696
+ }
4697
+ state.raw.push(line);
4698
+ const colon = line.indexOf(":");
4699
+ const fieldName = colon === -1 ? line : line.slice(0, colon);
4700
+ let value = colon === -1 ? "" : line.slice(colon + 1);
4701
+ if (value.charCodeAt(0) === 32) value = value.slice(1);
4702
+ if (fieldName === "event") state.event = value;
4703
+ else if (fieldName === "data") {
4704
+ if (state.data === null) state.data = value;
4705
+ else {
4706
+ state.data += "\n";
4707
+ state.data += value;
4708
+ }
4709
+ } else if (fieldName === "id") {
4710
+ if (!value.includes("\0")) state.id = value;
4711
+ } else if (fieldName === "retry" && value.length > 0) {
4712
+ let valid = true;
4713
+ for (let index = 0; index < value.length; index++) {
4714
+ const code = value.charCodeAt(index);
4715
+ if (code < 48 || code > 57) {
4716
+ valid = false;
4717
+ break;
4718
+ }
4719
+ }
4720
+ if (valid) {
4721
+ const retry = Number(value);
4722
+ if (Number.isSafeInteger(retry)) state.retry = retry;
4723
+ }
4724
+ }
4725
+ return null;
4726
+ }
4727
+ /**
4728
+ * Stream raw Server-Sent Events from an HTTP response body.
4729
+ *
4730
+ * Yields one `ServerSentEvent` per blank-line dispatch. The consumer is
4731
+ * responsible for parsing `data` (e.g. JSON, plain text, error envelope).
4732
+ * Use `readSseJson` instead when every event is a single `data:` JSON object
4733
+ * and you don't need access to the `event:` field.
4734
+ *
4735
+ * Internally backed by a Buffer-based reader (`ConcatSink`) that batches all
4736
+ * complete lines in each source chunk into one UTF-8 decode.
4737
+ *
4738
+ * @example
4739
+ * ```ts
4740
+ * for await (const sse of readSseEvents(response.body!)) {
4741
+ * if (sse.event === "ping") continue;
4742
+ * const obj = JSON.parse(sse.data);
4743
+ * }
4744
+ * ```
4745
+ */
4746
+ async function* readSseEvents(stream, signal) {
4747
+ const lineBuffer = new ConcatSink();
4748
+ const state = {
4749
+ event: null,
4750
+ data: null,
4751
+ raw: []
4752
+ };
4753
+ const source = abortableSource(stream, signal);
4754
+ try {
4755
+ for await (const chunk of source) {
4756
+ const text = lineBuffer.appendAndFlushText(chunk, SSE_DECODER);
4757
+ if (text === void 0) continue;
4758
+ let start = 0;
4759
+ while (start < text.length) {
4760
+ const newline = text.indexOf("\n", start);
4761
+ const event = pushSseLine(text.slice(start, newline), state);
4762
+ if (event) yield event;
4763
+ start = newline + 1;
4764
+ }
4765
+ }
4766
+ if (!lineBuffer.isEmpty) {
4767
+ const tail = lineBuffer.flush();
4768
+ if (tail) {
4769
+ lineBuffer.clear();
4770
+ const event = pushSseLine(SSE_DECODER.decode(tail), state);
4771
+ if (event) {
4772
+ trailingEvents.add(event);
4773
+ yield event;
4774
+ }
4775
+ }
4776
+ }
4777
+ const trailing = flushSseEvent(state);
4778
+ if (trailing) {
4779
+ trailingEvents.add(trailing);
4780
+ yield trailing;
4781
+ }
4782
+ } catch (err) {
4783
+ if (signal?.aborted) return;
4784
+ throw err;
4785
+ }
4786
+ }
4787
+ //#endregion
4788
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/temp.ts
4789
+ var TempDir = class TempDir {
4790
+ #path;
4791
+ constructor(path) {
4792
+ this.#path = path;
4793
+ }
4794
+ static createSync(prefix) {
4795
+ return new TempDir(fs.mkdtempSync(normalizePrefix(prefix)));
4796
+ }
4797
+ static async create(prefix) {
4798
+ return new TempDir(await fs_promises_shim_exports.mkdtemp(normalizePrefix(prefix)));
4799
+ }
4800
+ #removePromise = null;
4801
+ path() {
4802
+ return this.#path;
4803
+ }
4804
+ absolute() {
4805
+ return path.resolve(this.#path);
4806
+ }
4807
+ remove() {
4808
+ if (this.#removePromise) return this.#removePromise;
4809
+ const removePromise = removeWithRetries(this.#path);
4810
+ this.#removePromise = removePromise;
4811
+ return removePromise;
4812
+ }
4813
+ removeSync() {
4814
+ removeSyncWithRetries(this.#path);
4815
+ this.#removePromise = Promise.resolve();
4816
+ }
4817
+ toString() {
4818
+ return this.#path;
4819
+ }
4820
+ join(...paths) {
4821
+ return path.join(this.#path, ...paths);
4822
+ }
4823
+ async [Symbol.asyncDispose]() {
4824
+ try {
4825
+ await this.remove();
4826
+ } catch {}
4827
+ }
4828
+ [Symbol.dispose]() {
4829
+ try {
4830
+ this.removeSync();
4831
+ } catch {}
4832
+ }
4833
+ };
4834
+ const kTempDir = os.tmpdir();
4835
+ function normalizePrefix(prefix) {
4836
+ if (!prefix) return `${kTempDir}${path.sep}pi-temp-`;
4837
+ else if (prefix.startsWith("@")) return path.join(kTempDir, prefix.slice(1));
4838
+ return prefix;
4839
+ }
4840
+ const kRemoveOptions = {
4841
+ recursive: true,
4842
+ force: true
4843
+ };
4844
+ const kRemoveRetries = 40;
4845
+ const kRemoveRetryDelayMs = 50;
4846
+ const kRetryableRemoveErrorCodes = /* @__PURE__ */ new Set([
4847
+ "EBUSY",
4848
+ "EPERM",
4849
+ "ENOTEMPTY"
4850
+ ]);
4851
+ const kSleepBuffer = new Int32Array(new SharedArrayBuffer(4));
4852
+ /** Removes a path recursively, retrying transient Windows deletion failures. */
4853
+ async function removeWithRetries(target) {
4854
+ for (let attempt = 0;; attempt++) try {
4855
+ await fs_promises_shim_exports.rm(target, kRemoveOptions);
4856
+ return;
4857
+ } catch (err) {
4858
+ if (!shouldRetryRemove(err, attempt)) throw err;
4859
+ await Bun.sleep(kRemoveRetryDelayMs);
4860
+ }
4861
+ }
4862
+ function removeSyncWithRetries(target) {
4863
+ for (let attempt = 0;; attempt++) try {
4864
+ fs.rmSync(target, kRemoveOptions);
4865
+ return;
4866
+ } catch (err) {
4867
+ if (!shouldRetryRemove(err, attempt)) throw err;
4868
+ sleepSync(kRemoveRetryDelayMs);
4869
+ }
4870
+ }
4871
+ function shouldRetryRemove(err, attempt) {
4872
+ return attempt < kRemoveRetries && process.platform === "win32" && isRetryableRemoveError(err);
4873
+ }
4874
+ function isRetryableRemoveError(err) {
4875
+ return typeof err === "object" && err !== null && "code" in err && typeof err.code === "string" && kRetryableRemoveErrorCodes.has(err.code);
4876
+ }
4877
+ function sleepSync(ms) {
4878
+ if ("sleepSync" in Bun && typeof Bun.sleepSync === "function") {
4879
+ Bun.sleepSync(ms);
4880
+ return;
4881
+ }
4882
+ Atomics.wait(kSleepBuffer, 0, 0, ms);
4883
+ }
4884
+ //#endregion
4885
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/tls-fetch.ts
4886
+ /**
4887
+ * `NODE_EXTRA_CA_CERTS` shim for Bun's `fetch`.
4888
+ *
4889
+ * Node's TLS layer honours `NODE_EXTRA_CA_CERTS` natively, but Bun's
4890
+ * `fetch` does not, and both the provider streams (`openai-responses`,
4891
+ * `openai-completions`, `openai-codex-responses`, `ollama-chat`, ...) and
4892
+ * catalog model discovery (`/models` probes) route every request through
4893
+ * Bun's runtime. Without this wrapper, corporate relays and private
4894
+ * gateways behind a custom CA bundle fail with
4895
+ * `unknown certificate verification error` even when the env var is set.
4896
+ *
4897
+ * The wrapper merges the resolved CA bundle into Bun's `RequestInit.tls.ca`.
4898
+ * Bun's `tls.ca` REPLACES the default trust store when set, so the wrapper
4899
+ * always seeds {@link tls.rootCertificates} when the caller has not already
4900
+ * curated their own CA list.
4901
+ */
4902
+ /**
4903
+ * `NODE_EXTRA_CA_CERTS` was set but unusable (path does not exist). This is
4904
+ * a config/contract error, not a transient transport fault — it is never
4905
+ * retried.
4906
+ */
4907
+ var ExtraCaError = class extends Error {
4908
+ constructor(message, options) {
4909
+ super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
4910
+ this.name = "ExtraCaError";
4911
+ }
4912
+ };
4913
+ const EXTRA_CA_FETCH_MARKER = Symbol("omp.extraCaFetch");
4914
+ /**
4915
+ * Cached resolution of `NODE_EXTRA_CA_CERTS`. Keyed on the env value plus
4916
+ * the file mtime for path values so on-disk cert rotation (short-lived
4917
+ * corporate bundles) invalidates the cache instead of pinning the first
4918
+ * read forever.
4919
+ */
4920
+ let cacheKey;
4921
+ let cacheValue;
4922
+ /**
4923
+ * Returns the PEM bytes referenced by `NODE_EXTRA_CA_CERTS`, or `undefined`
4924
+ * when the env var is unset/empty.
4925
+ *
4926
+ * Accepts the same shapes Node accepts plus an inline-PEM escape hatch:
4927
+ * - Inline PEM (`-----BEGIN CERTIFICATE-----...`). Literal `\n` escapes in
4928
+ * the env value are expanded so callers can ship single-line PEMs through
4929
+ * shell exports.
4930
+ * - File path. Anything that does not contain a PEM header is treated as a
4931
+ * path, matching Node's "extensionless filename is still a path" contract.
4932
+ * `ENOENT` becomes {@link ExtraCaError}; other I/O errors bubble.
4933
+ */
4934
+ function resolveExtraCa() {
4935
+ const raw = $env.NODE_EXTRA_CA_CERTS?.trim();
4936
+ if (!raw) return void 0;
4937
+ let key;
4938
+ if (raw.includes("-----BEGIN")) key = raw;
4939
+ else try {
4940
+ key = `${raw}@${fs.statSync(raw).mtimeMs}`;
4941
+ } catch {
4942
+ key = raw;
4943
+ }
4944
+ if (key === cacheKey) return cacheValue;
4945
+ if (raw.includes("-----BEGIN")) cacheValue = raw.replace(/\\n/g, "\n");
4946
+ else try {
4947
+ cacheValue = fs.readFileSync(raw, "utf8");
4948
+ } catch (error) {
4949
+ if (isEnoent(error)) throw new ExtraCaError(`NODE_EXTRA_CA_CERTS path does not exist: ${raw}`);
4950
+ throw error;
4951
+ }
4952
+ cacheKey = key;
4953
+ return cacheValue;
4954
+ }
4955
+ /**
4956
+ * Merge `extraCa` into `init.tls.ca`. When the caller has not supplied a CA
4957
+ * list, the system root store is included alongside the extra bundle —
4958
+ * Bun's `tls.ca` replaces the default trust store, so omitting roots would
4959
+ * break every public host. When the caller already curated a list (e.g.
4960
+ * Anthropic Foundry's mTLS options, which already seed
4961
+ * `tls.rootCertificates`), only the extra CA is appended.
4962
+ */
4963
+ function withExtraCaInit(init, extraCa) {
4964
+ const existingTls = init?.tls;
4965
+ const existingCa = existingTls?.ca;
4966
+ let mergedCa;
4967
+ if (existingCa === void 0) mergedCa = [...tls.rootCertificates, extraCa];
4968
+ else if (Array.isArray(existingCa)) mergedCa = [...existingCa, extraCa];
4969
+ else mergedCa = [existingCa, extraCa];
4970
+ return {
4971
+ ...init,
4972
+ tls: {
4973
+ ...existingTls,
4974
+ ca: mergedCa
4975
+ }
4976
+ };
4977
+ }
4978
+ /**
4979
+ * Wrap `fetchImpl` so every call honours `NODE_EXTRA_CA_CERTS`. Idempotent:
4980
+ * a fetch already wrapped is returned unchanged so repeated composition
4981
+ * (Anthropic auth-retry replays, request-debug fan-out) never stacks
4982
+ * wrappers. When the env var is unset the original fetch is returned, so
4983
+ * default deployments pay nothing.
4984
+ */
4985
+ function wrapFetchForExtraCa(fetchImpl) {
4986
+ if (fetchImpl[EXTRA_CA_FETCH_MARKER]) return fetchImpl;
4987
+ if (!$env.NODE_EXTRA_CA_CERTS?.trim()) return fetchImpl;
4988
+ return Object.assign(async (input, init) => {
4989
+ const extraCa = resolveExtraCa();
4990
+ return extraCa ? fetchImpl(input, withExtraCaInit(init, extraCa)) : fetchImpl(input, init);
4991
+ }, fetchImpl.preconnect ? { preconnect: fetchImpl.preconnect } : {}, { [EXTRA_CA_FETCH_MARKER]: true });
4992
+ }
4993
+ //#endregion
4994
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/index.ts
4995
+ function isPlainObject(val) {
4996
+ return Object.getPrototypeOf(val) === Object.prototype || Array.isArray(val);
4997
+ }
4998
+ function structuredCloneJSON(value) {
4999
+ if (!value || typeof value !== "object") return value;
5000
+ if (isPlainObject(value)) try {
5001
+ return structuredClone(value);
5002
+ } catch {}
5003
+ return JSON.parse(JSON.stringify(value));
5004
+ }
5005
+ //#endregion
5006
+ export { once as $, formatAge as A, USER_AGENT as B, error as C, Cookie as D, $ as E, $envpos as F, getDocsRsCacheDir as G, getAgentDir as H, $flag as I, getLogsDir as J, getDocumentConversionCacheDir as K, $pickenv as L, formatCount as M, formatNumber as N, CookieMap as O, $env as P, peekFile as Q, isBunTestRuntime as R, debug as S, warn as T, getAutoQaDbPath as U, VERSION as V, getDebugLogPath as W, getToolsDir as X, getRemoteDir as Y, isProbablyBinary as Z, parseStreamingJsonThrottled as _, readSseEvents as a, isEnotdir as b, combineSignals as c, $which as d, untilAborted as et, parseImageMetadata as f, parseStreamingJson as g, parseJsonWithRepair as h, readJsonl as i, formatBytes as j, installBunShim as k, exec as l, classifyJsonPrefix as m, wrapFetchForExtraCa as n, readSseJson as o, readImageMetadata as p, getInstallId as q, TempDir as r, Snowflake as s, structuredCloneJSON as t, render as u, hasFsCode as v, time as w, parseFrontmatter as x, isEnoent as y, isTerminalHeadless as z };