@letrquan/book 0.3.0 → 0.4.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/CHANGELOG.md +1024 -0
  2. package/README.md +6 -3
  3. package/dist/{additional-roots-H7UNAEHS.js → additional-roots-VAWRQ625.js} +8 -7
  4. package/dist/{app-GE4OHJOC.js → app-J7BVPSUH.js} +5397 -2953
  5. package/dist/app-J7BVPSUH.js.map +1 -0
  6. package/dist/chunk-32WBCUAN.js +660 -0
  7. package/dist/chunk-32WBCUAN.js.map +1 -0
  8. package/dist/{chunk-525CAZPY.js → chunk-4DKHLZRI.js} +6 -6
  9. package/dist/{chunk-4AS7LKKQ.js → chunk-62EEAUID.js} +2 -2
  10. package/dist/{chunk-AQEQX4FF.js → chunk-AHC7OU75.js} +385 -55
  11. package/dist/chunk-AHC7OU75.js.map +1 -0
  12. package/dist/{chunk-EFJ2NH6E.js → chunk-AJZWLKLQ.js} +63 -14
  13. package/dist/chunk-AJZWLKLQ.js.map +1 -0
  14. package/dist/chunk-B5FC3MYZ.js +395 -0
  15. package/dist/chunk-B5FC3MYZ.js.map +1 -0
  16. package/dist/{chunk-67UZEWWF.js → chunk-DNFQTSMJ.js} +53 -11
  17. package/dist/chunk-DNFQTSMJ.js.map +1 -0
  18. package/dist/{chunk-TLX3H2CL.js → chunk-E5WLQSKA.js} +231 -40
  19. package/dist/chunk-E5WLQSKA.js.map +1 -0
  20. package/dist/chunk-ECLC4CIJ.js +1181 -0
  21. package/dist/chunk-ECLC4CIJ.js.map +1 -0
  22. package/dist/chunk-EEX6QNXV.js +127 -0
  23. package/dist/chunk-EEX6QNXV.js.map +1 -0
  24. package/dist/chunk-EFXVSQ5J.js +931 -0
  25. package/dist/chunk-EFXVSQ5J.js.map +1 -0
  26. package/dist/{chunk-RFJ6Q6LE.js → chunk-ENLB7NYV.js} +577 -164
  27. package/dist/chunk-ENLB7NYV.js.map +1 -0
  28. package/dist/{chunk-IME7R2UI.js → chunk-FPR7SKIE.js} +1268 -883
  29. package/dist/chunk-FPR7SKIE.js.map +1 -0
  30. package/dist/{chunk-VPNLCMVR.js → chunk-HFB74CLM.js} +13 -5
  31. package/dist/chunk-HFB74CLM.js.map +1 -0
  32. package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
  33. package/dist/chunk-HOZXKOE3.js.map +1 -0
  34. package/dist/chunk-IDYWN37T.js +36 -0
  35. package/dist/chunk-IDYWN37T.js.map +1 -0
  36. package/dist/{chunk-YC6W4EJH.js → chunk-JGF5HLSI.js} +193 -75
  37. package/dist/chunk-JGF5HLSI.js.map +1 -0
  38. package/dist/{chunk-5YHTKWJN.js → chunk-KDNWMKXV.js} +2447 -907
  39. package/dist/chunk-KDNWMKXV.js.map +1 -0
  40. package/dist/{chunk-Y2VHN7TC.js → chunk-MEOB2DZA.js} +11 -1
  41. package/dist/chunk-MEOB2DZA.js.map +1 -0
  42. package/dist/{chunk-KBQS35OV.js → chunk-MV5PQNIA.js} +2 -2
  43. package/dist/chunk-N2G4Q7ME.js +2672 -0
  44. package/dist/chunk-N2G4Q7ME.js.map +1 -0
  45. package/dist/{chunk-62QL5QUP.js → chunk-OJBMDM5H.js} +17 -1
  46. package/dist/chunk-OJBMDM5H.js.map +1 -0
  47. package/dist/{chunk-56SIEMDX.js → chunk-PTDC7ZUP.js} +2 -2
  48. package/dist/{chunk-X4NJT436.js → chunk-REASC2QG.js} +67 -28
  49. package/dist/chunk-REASC2QG.js.map +1 -0
  50. package/dist/chunk-SBTU5BEC.js +114 -0
  51. package/dist/chunk-SBTU5BEC.js.map +1 -0
  52. package/dist/{chunk-HQI7NYGX.js → chunk-SWPDKLNM.js} +2 -2
  53. package/dist/{chunk-W3XFHEF4.js → chunk-UCFJ43OS.js} +140 -28
  54. package/dist/chunk-UCFJ43OS.js.map +1 -0
  55. package/dist/{chunk-QYBD22H5.js → chunk-W7SLYS3M.js} +80 -16
  56. package/dist/chunk-W7SLYS3M.js.map +1 -0
  57. package/dist/{chunk-SOW4WRQP.js → chunk-WV3O5PGX.js} +3 -3
  58. package/dist/{chunk-JTPOI3KU.js → chunk-XJRJVXJR.js} +262 -38
  59. package/dist/chunk-XJRJVXJR.js.map +1 -0
  60. package/dist/{chunk-224FJZLH.js → chunk-YIL6HTJX.js} +228 -5
  61. package/dist/chunk-YIL6HTJX.js.map +1 -0
  62. package/dist/{command-approvals-VBIU6BLM.js → command-approvals-ZRY66LCW.js} +5 -4
  63. package/dist/context-C3RAP6K5.js +41 -0
  64. package/dist/{hook-approvals-ZVT4V7IM.js → hook-approvals-DPQD5H65.js} +5 -4
  65. package/dist/index.js +657 -754
  66. package/dist/index.js.map +1 -1
  67. package/dist/{interactive-assets-T4I4SEHU.js → interactive-assets-73AQO5WP.js} +11 -6
  68. package/dist/{interactive-assets-T4I4SEHU.js.map → interactive-assets-73AQO5WP.js.map} +1 -1
  69. package/dist/job-runner.js +10 -3
  70. package/dist/job-runner.js.map +1 -1
  71. package/dist/job-supervisor.js +13 -4
  72. package/dist/job-supervisor.js.map +1 -1
  73. package/dist/loader-DQNPDKOX.js +27 -0
  74. package/dist/{mcp-approvals-HJ2VDYMC.js → mcp-approvals-27LJYADQ.js} +5 -4
  75. package/dist/{mcp-config-RNB5LEVE.js → mcp-config-GXSODNCM.js} +3 -2
  76. package/dist/memory-store-I6Z54VUN.js +51 -0
  77. package/dist/{model-window-store-MTSQ5JDJ.js → model-window-store-6VKXXTPJ.js} +4 -3
  78. package/dist/{openai-compatible-2OB54YST.js → openai-compatible-IPUNBJ53.js} +5 -3
  79. package/dist/{permission-approvals-YLV6UC5W.js → permission-approvals-Q5IMFEZI.js} +5 -4
  80. package/dist/{permissions-DHOKF3W2.js → permissions-7Y35X6HZ.js} +26 -9
  81. package/dist/provider-approvals-5AGH3RVT.js +76 -0
  82. package/dist/{sandbox-WRYTZH3D.js → sandbox-N2RY72GN.js} +6 -5
  83. package/dist/{scrollback-35ME2GFX.js → scrollback-GD76GNM4.js} +36 -28
  84. package/dist/scrollback-GD76GNM4.js.map +1 -0
  85. package/dist/sdk.d.ts +876 -64
  86. package/dist/sdk.js +57 -37
  87. package/dist/sdk.js.map +1 -1
  88. package/dist/settings-loader-KDBH45AQ.js +43 -0
  89. package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
  90. package/dist/shell-selection-Z5WORZU4.js +31 -0
  91. package/dist/shell-selection-Z5WORZU4.js.map +1 -0
  92. package/package.json +5 -5
  93. package/dist/app-GE4OHJOC.js.map +0 -1
  94. package/dist/chunk-224FJZLH.js.map +0 -1
  95. package/dist/chunk-5RDRAO4B.js.map +0 -1
  96. package/dist/chunk-5YHTKWJN.js.map +0 -1
  97. package/dist/chunk-62QL5QUP.js.map +0 -1
  98. package/dist/chunk-67UZEWWF.js.map +0 -1
  99. package/dist/chunk-6OQQOMZR.js +0 -785
  100. package/dist/chunk-6OQQOMZR.js.map +0 -1
  101. package/dist/chunk-AQEQX4FF.js.map +0 -1
  102. package/dist/chunk-C7ADUNPR.js +0 -273
  103. package/dist/chunk-C7ADUNPR.js.map +0 -1
  104. package/dist/chunk-EFJ2NH6E.js.map +0 -1
  105. package/dist/chunk-IME7R2UI.js.map +0 -1
  106. package/dist/chunk-J62VZYOB.js +0 -1339
  107. package/dist/chunk-J62VZYOB.js.map +0 -1
  108. package/dist/chunk-JTPOI3KU.js.map +0 -1
  109. package/dist/chunk-QYBD22H5.js.map +0 -1
  110. package/dist/chunk-RFJ6Q6LE.js.map +0 -1
  111. package/dist/chunk-S4JMID4G.js +0 -259
  112. package/dist/chunk-S4JMID4G.js.map +0 -1
  113. package/dist/chunk-TLX3H2CL.js.map +0 -1
  114. package/dist/chunk-VPNLCMVR.js.map +0 -1
  115. package/dist/chunk-W3XFHEF4.js.map +0 -1
  116. package/dist/chunk-X4NJT436.js.map +0 -1
  117. package/dist/chunk-X66I2NQO.js +0 -137
  118. package/dist/chunk-X66I2NQO.js.map +0 -1
  119. package/dist/chunk-Y2VHN7TC.js.map +0 -1
  120. package/dist/chunk-YC6W4EJH.js.map +0 -1
  121. package/dist/context-CBOBDY5Q.js +0 -38
  122. package/dist/loader-S77OVTHH.js +0 -18
  123. package/dist/memory-store-DZUT5HUH.js +0 -75
  124. package/dist/scrollback-35ME2GFX.js.map +0 -1
  125. package/dist/settings-loader-JUNPGAAM.js +0 -35
  126. package/dist/shell-selection-CDHGJ4GC.js +0 -17
  127. /package/dist/{additional-roots-H7UNAEHS.js.map → additional-roots-VAWRQ625.js.map} +0 -0
  128. /package/dist/{chunk-525CAZPY.js.map → chunk-4DKHLZRI.js.map} +0 -0
  129. /package/dist/{chunk-4AS7LKKQ.js.map → chunk-62EEAUID.js.map} +0 -0
  130. /package/dist/{chunk-KBQS35OV.js.map → chunk-MV5PQNIA.js.map} +0 -0
  131. /package/dist/{chunk-56SIEMDX.js.map → chunk-PTDC7ZUP.js.map} +0 -0
  132. /package/dist/{chunk-HQI7NYGX.js.map → chunk-SWPDKLNM.js.map} +0 -0
  133. /package/dist/{chunk-SOW4WRQP.js.map → chunk-WV3O5PGX.js.map} +0 -0
  134. /package/dist/{command-approvals-VBIU6BLM.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
  135. /package/dist/{context-CBOBDY5Q.js.map → context-C3RAP6K5.js.map} +0 -0
  136. /package/dist/{hook-approvals-ZVT4V7IM.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
  137. /package/dist/{loader-S77OVTHH.js.map → loader-DQNPDKOX.js.map} +0 -0
  138. /package/dist/{mcp-approvals-HJ2VDYMC.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
  139. /package/dist/{mcp-config-RNB5LEVE.js.map → mcp-config-GXSODNCM.js.map} +0 -0
  140. /package/dist/{memory-store-DZUT5HUH.js.map → memory-store-I6Z54VUN.js.map} +0 -0
  141. /package/dist/{model-window-store-MTSQ5JDJ.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
  142. /package/dist/{openai-compatible-2OB54YST.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
  143. /package/dist/{permission-approvals-YLV6UC5W.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
  144. /package/dist/{permissions-DHOKF3W2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
  145. /package/dist/{sandbox-WRYTZH3D.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
  146. /package/dist/{settings-loader-JUNPGAAM.js.map → sandbox-N2RY72GN.js.map} +0 -0
  147. /package/dist/{settings-removed-JWWPT4WA.js.map → settings-loader-KDBH45AQ.js.map} +0 -0
  148. /package/dist/{shell-selection-CDHGJ4GC.js.map → settings-removed-3ATWJEF3.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider/model-discovery.ts","../src/provider-key.ts","../src/cli/ink-renderer.ts","../src/cli/ink-scroll-renderer.ts","../src/cli/frame-buffer.ts","../src/cli/ink-output-cache.ts","../src/cli/ink-render-cull.ts","../src/cli/ink-frame-clip.ts","../src/cli/tui-renderer-mode.ts"],"sourcesContent":["import type { RetryConfig } from '../types/runtime.js';\n\nexport type ProviderProtocol = 'openai' | 'anthropic';\n\nexport interface DiscoveredModel {\n id: string;\n label?: string;\n /**\n * The model's input limit as the endpoint reported it, from Anthropic's `max_input_tokens`.\n * Present only for a valid figure from an Anthropic endpoint: an OpenAI-compatible router's\n * figure is not authoritative. It is stored apart from the user's `contextWindow`.\n */\n reportedContextWindow?: number;\n}\n\nimport { systemClock, type Clock } from '../clock.js';\nimport { isRedirectResponse, refuseRedirect, type ProviderRedirectError } from './reliability.js';\n\nexport interface ModelDiscoveryOptions {\n type: ProviderProtocol;\n baseUrl: string;\n apiKey: string;\n retry: RetryConfig;\n signal?: AbortSignal;\n fetchImpl?: typeof fetch;\n onRetry?: (attempt: number, max: number, delayMs: number) => void;\n /** Injected by tests; the retry budget below is a duration, not a stamp. */\n clock?: Clock;\n}\n\nexport const DEFAULT_PROVIDER_BASE_URLS: Record<ProviderProtocol, string> = {\n openai: 'https://api.openai.com/v1',\n anthropic: 'https://api.anthropic.com',\n};\n\nconst RETRYABLE_STATUSES = new Set([408, 429, 529]);\nconst MAX_ANTHROPIC_PAGES = 100;\n\nexport class ModelDiscoveryError extends Error {\n constructor(\n message: string,\n readonly code: 'aborted' | 'auth' | 'unsupported' | 'invalid_response' | 'network' | 'request',\n ) {\n super(message);\n this.name = 'ModelDiscoveryError';\n }\n}\n\nexport function normalizeProviderBaseUrl(_type: ProviderProtocol, raw: string): string {\n const trimmed = raw.trim();\n let url: URL;\n try {\n url = new URL(trimmed);\n } catch {\n throw new ModelDiscoveryError('Enter a valid HTTP(S) base URL.', 'request');\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new ModelDiscoveryError('Base URL must use HTTP or HTTPS.', 'request');\n }\n if (url.username || url.password) {\n throw new ModelDiscoveryError('Base URL cannot contain embedded credentials.', 'request');\n }\n url.search = '';\n url.hash = '';\n url.pathname = url.pathname.replace(/\\/+$/, '') || '/';\n return url.toString().replace(/\\/$/, '');\n}\n\nfunction discoveryUrl(type: ProviderProtocol, baseUrl: string, afterId?: string): string {\n const base = normalizeProviderBaseUrl(type, baseUrl);\n if (type === 'openai') return `${base}/models`;\n const url = new URL(`${base}${/\\/v1$/i.test(new URL(base).pathname) ? '' : '/v1'}/models`);\n url.searchParams.set('limit', '100');\n if (afterId) url.searchParams.set('after_id', afterId);\n return url.toString();\n}\n\nfunction safeStatusError(status: number): ModelDiscoveryError {\n if (status === 401 || status === 403) {\n return new ModelDiscoveryError(\n 'Model discovery was not authorized. Check the API key and endpoint permissions.',\n 'auth',\n );\n }\n if (status === 404 || status === 405 || status === 501) {\n return new ModelDiscoveryError(\n 'This endpoint does not expose a supported model-list API.',\n 'unsupported',\n );\n }\n return new ModelDiscoveryError(`Model discovery request failed with HTTP ${status}.`, 'request');\n}\n\n/** One line for the wizard's and picker's error slot: discovery sends no prompt. */\nfunction redirectMessage({ status, target }: ProviderRedirectError): string {\n const http = status === 0 ? '' : ` (HTTP ${status})`;\n const what =\n target === undefined\n ? `answered${status === 0 ? '' : ` HTTP ${status}`} with no redirect target Book could read`\n : `redirected to ${target}${http}`;\n return `The model-list endpoint ${what}; Book does not follow provider redirects. Check the provider's base URL.`;\n}\n\nfunction retryDelay(attempt: number, retry: RetryConfig, retryAfter: string | null): number {\n const retryAfterSeconds = retryAfter === null ? NaN : Number(retryAfter);\n if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0) {\n return Math.min(retryAfterSeconds * 1000, retry.maxDelayMs);\n }\n return Math.min(retry.baseDelayMs * 2 ** attempt, retry.maxDelayMs);\n}\n\nfunction wait(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(new ModelDiscoveryError('Model discovery was cancelled.', 'aborted'));\n }\n return new Promise((resolve, reject) => {\n const abort = () => {\n clearTimeout(timer);\n reject(new ModelDiscoveryError('Model discovery was cancelled.', 'aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', abort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', abort, { once: true });\n });\n}\n\nasync function fetchWithRetry(\n url: string,\n init: RequestInit,\n options: ModelDiscoveryOptions,\n): Promise<Response> {\n const fetchImpl = options.fetchImpl ?? fetch;\n const clock = options.clock ?? systemClock;\n const startedAt = clock.monotonicNowMs();\n let lastNetworkError: unknown;\n\n for (let attempt = 0; attempt <= options.retry.maxAttempts; attempt++) {\n if (options.signal?.aborted) {\n throw new ModelDiscoveryError('Model discovery was cancelled.', 'aborted');\n }\n\n const controller = new AbortController();\n const abortFromCaller = () => controller.abort(options.signal?.reason);\n options.signal?.addEventListener('abort', abortFromCaller, { once: true });\n const timeout = setTimeout(\n () => controller.abort(new Error('request timeout')),\n options.retry.requestTimeoutMs,\n );\n\n let response: Response | undefined;\n try {\n // `redirect` comes after the spread so no caller can turn following back on (#445).\n response = await fetchImpl(url, { ...init, redirect: 'manual', signal: controller.signal });\n } catch (error) {\n if (options.signal?.aborted) {\n throw new ModelDiscoveryError('Model discovery was cancelled.', 'aborted');\n }\n lastNetworkError = error;\n } finally {\n clearTimeout(timeout);\n options.signal?.removeEventListener('abort', abortFromCaller);\n }\n\n // Never retried: the key must not follow a redirect, and a second ask gets the same answer.\n if (response && isRedirectResponse(response)) {\n const refused = await refuseRedirect(response, url);\n throw new ModelDiscoveryError(redirectMessage(refused), 'request');\n }\n\n if (response && !(RETRYABLE_STATUSES.has(response.status) || response.status >= 500)) {\n return response;\n }\n\n if (attempt >= options.retry.maxAttempts) {\n if (response) return response;\n throw new ModelDiscoveryError('Could not reach the model-list endpoint.', 'network');\n }\n if (\n options.retry.totalBudgetMs > 0 &&\n clock.monotonicNowMs() - startedAt >= options.retry.totalBudgetMs\n ) {\n if (response) return response;\n throw new ModelDiscoveryError('Could not reach the model-list endpoint.', 'network');\n }\n\n const delay = retryDelay(attempt, options.retry, response?.headers.get('retry-after') ?? null);\n options.onRetry?.(attempt + 1, options.retry.maxAttempts, delay);\n await wait(delay, options.signal);\n }\n\n throw new ModelDiscoveryError(\n lastNetworkError ? 'Could not reach the model-list endpoint.' : 'Model discovery failed.',\n 'network',\n );\n}\n\nfunction parseModels(payload: unknown, type: ProviderProtocol): DiscoveredModel[] {\n if (!payload || typeof payload !== 'object') {\n throw new ModelDiscoveryError(\n 'The model-list endpoint returned invalid JSON.',\n 'invalid_response',\n );\n }\n const data = (payload as { data?: unknown }).data;\n if (!Array.isArray(data)) {\n throw new ModelDiscoveryError(\n 'The model-list endpoint returned an unsupported response shape.',\n 'invalid_response',\n );\n }\n\n const models: DiscoveredModel[] = [];\n for (const item of data) {\n if (!item || typeof item !== 'object') continue;\n const record = item as Record<string, unknown>;\n const id = typeof record.id === 'string' ? record.id.trim() : '';\n if (!id || /[\\x00-\\x1f\\x7f]/.test(id)) continue;\n const rawLabel =\n type === 'anthropic'\n ? record.display_name\n : (record.name ?? record.display_name ?? record.label);\n const label = typeof rawLabel === 'string' && rawLabel.trim() ? rawLabel.trim() : undefined;\n const model: DiscoveredModel = { id, label };\n const limit = record.max_input_tokens;\n if (\n type === 'anthropic' &&\n typeof limit === 'number' &&\n Number.isSafeInteger(limit) &&\n limit > 0\n ) {\n model.reportedContextWindow = limit;\n }\n models.push(model);\n }\n return models;\n}\n\nexport async function discoverModels(options: ModelDiscoveryOptions): Promise<DiscoveredModel[]> {\n const headers: Record<string, string> =\n options.type === 'anthropic'\n ? {\n 'x-api-key': options.apiKey,\n 'anthropic-version': '2023-06-01',\n }\n : { Authorization: `Bearer ${options.apiKey}` };\n\n const discovered: DiscoveredModel[] = [];\n let afterId: string | undefined;\n let finished = false;\n\n for (let page = 0; page < MAX_ANTHROPIC_PAGES; page++) {\n const response = await fetchWithRetry(\n discoveryUrl(options.type, options.baseUrl, afterId),\n { method: 'GET', headers },\n options,\n );\n if (!response.ok) throw safeStatusError(response.status);\n\n let payload: unknown;\n try {\n payload = await response.json();\n } catch {\n throw new ModelDiscoveryError(\n 'The model-list endpoint returned invalid JSON.',\n 'invalid_response',\n );\n }\n discovered.push(...parseModels(payload, options.type));\n\n if (options.type !== 'anthropic') {\n finished = true;\n break;\n }\n const pageInfo = payload as { has_more?: unknown; last_id?: unknown };\n if (pageInfo.has_more !== true) {\n finished = true;\n break;\n }\n const nextId = typeof pageInfo.last_id === 'string' ? pageInfo.last_id : undefined;\n if (!nextId || nextId === afterId) {\n throw new ModelDiscoveryError(\n 'The model-list endpoint returned invalid pagination metadata.',\n 'invalid_response',\n );\n }\n afterId = nextId;\n }\n\n if (options.type === 'anthropic' && !finished) {\n throw new ModelDiscoveryError(\n 'The model-list endpoint exceeded the pagination limit.',\n 'invalid_response',\n );\n }\n\n const deduped = new Map<string, DiscoveredModel>();\n for (const model of discovered) {\n const existing = deduped.get(model.id);\n if (!existing) {\n deduped.set(model.id, model);\n continue;\n }\n // Field by field, first non-empty value wins: the entries may each know a different part.\n const label = existing.label || model.label;\n const reportedContextWindow = existing.reportedContextWindow ?? model.reportedContextWindow;\n const merged: DiscoveredModel = { id: model.id, label };\n if (reportedContextWindow !== undefined) merged.reportedContextWindow = reportedContextWindow;\n deduped.set(model.id, merged);\n }\n const models = [...deduped.values()].sort((a, b) => a.id.localeCompare(b.id));\n if (models.length === 0) {\n throw new ModelDiscoveryError(\n 'The endpoint did not return any usable models.',\n 'invalid_response',\n );\n }\n return models;\n}\n","/**\n * Replace the API key of a provider the user configured (#592), for `/model`'s `Alt+K` sheet and\n * `book config provider-key` alike. It lives outside `tui/` because the CLI must not import the\n * TUI, which is a leaf of the import graph.\n *\n * Three rules hold for both surfaces:\n *\n * - **Only the user file's providers.** `<BOOK_HOME>/settings.json` is the user's own; a provider\n * only a repository declares stays the repository's, on the ownership rule `Alt+D`, `Alt+R` and\n * `Alt+M` use.\n * - **Only `apiKey` changes.** The entry is edited as raw JSON, so the catalog (discovered and\n * `\"manual\": true` models), the active model and every per-model override stay byte for byte,\n * and a hand-written entry gains no schema defaults. The local half follows the `/model` save\n * rule: a same-named entry in `.book/settings.local.json` loses its connection keys, so a stale\n * local key cannot look live, and no local entry is ever created.\n * - **The key goes nowhere but the provider's own endpoint.** The check calls the configured base\n * URL's model-list API, never a default host or `BOOK_BASE_URL`, and no error this module returns\n * contains the key.\n */\nimport { existsSync } from 'fs';\nimport { resolveSecret } from './config.js';\nimport { deleteNestedValue } from './cli/utils.js';\nimport { PROVIDER_CONNECTION_KEYS } from './provider-approvals.js';\nimport { redactSecrets } from './provider-secrets.js';\nimport {\n discoverModels,\n type DiscoveredModel,\n type ModelDiscoveryOptions,\n} from './provider/model-discovery.js';\nimport { parseSecretReference } from './secret-reference.js';\nimport { providerConfigSchema, type ProviderConfig } from './settings.js';\nimport {\n formatSettingsDiagnostics,\n readSettingsDocument,\n SettingsRepository,\n} from './settings-repository.js';\nimport { settingsScopePath } from './settings-scope.js';\nimport type { RetryConfig } from './types/runtime.js';\nimport { projectFileWriteRefusal } from './workspace-files.js';\n\n/** What the add-provider wizard's key step accepts: a literal key, `{env:VAR}` or `{file:path}`. */\nexport function validateApiKey(raw: string): string {\n if (!raw.trim()) throw new Error('API key is required.');\n if (/[\\r\\n\\x00]/.test(raw)) throw new Error('API key cannot contain line breaks.');\n return raw;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** The entry `document.provider[providerId]`, own properties only. */\nfunction providerEntryOf(\n document: Record<string, unknown>,\n providerId: string,\n): Record<string, unknown> | undefined {\n const registry = document.provider;\n if (!isPlainObject(registry) || !Object.hasOwn(registry, providerId)) return undefined;\n const entry = registry[providerId];\n return isPlainObject(entry) ? entry : undefined;\n}\n\nexport type ProviderKeyTarget =\n /** The user file defines it: its key may be replaced. */\n | { kind: 'owned'; providerId: string; path: string; provider: ProviderConfig }\n /** Only a workspace layer declares it. `declaredIn` is the first file that does. */\n | { kind: 'workspace-only'; providerId: string; path: string; declaredIn: string }\n /** Nothing declares it. `configured` lists the user file's provider ids. */\n | { kind: 'unknown'; providerId: string; path: string; configured: string[] }\n /** The user file, or its entry for this id, cannot be read as settings. */\n | { kind: 'unreadable'; providerId: string; path: string; error: string };\n\n/**\n * Whose provider `providerId` is. Reads the layers as documents rather than resolving them, so a\n * malformed workspace layer or a missing credential does not stop a key update: that is the state\n * a user reaching for one is often in.\n */\nexport function providerKeyTarget(workspace: string, providerId: string): ProviderKeyTarget {\n const path = settingsScopePath('user', workspace);\n const user = readSettingsDocument(path);\n if (user.status === 'malformed' || user.status === 'non-object') {\n return { kind: 'unreadable', providerId, path, error: user.error };\n }\n const document = user.status === 'valid' ? user.document : {};\n if (isPlainObject(document.provider) && Object.hasOwn(document.provider, providerId)) {\n // The key itself is left out: a broken `apiKey` (empty, null, a number) is exactly what this\n // update repairs, so it must not stop the update. Only the endpoint and protocol are read here.\n const entry = document.provider[providerId];\n const parsed = providerConfigSchema.safeParse(\n isPlainObject(entry) ? { ...entry, apiKey: undefined } : entry,\n );\n if (!parsed.success) {\n return {\n kind: 'unreadable',\n providerId,\n path,\n error: `Provider \"${providerId}\" is not a valid provider entry: ${parsed.error.issues\n .map((issue) => `${issue.path.join('.') || '(entry)'}: ${issue.message}`)\n .join('; ')}`,\n };\n }\n return { kind: 'owned', providerId, path, provider: parsed.data };\n }\n for (const scope of ['project', 'local'] as const) {\n const workspacePath = settingsScopePath(scope, workspace);\n const layer = readSettingsDocument(workspacePath);\n if (layer.status === 'valid' && providerEntryOf(layer.document, providerId)) {\n return { kind: 'workspace-only', providerId, path, declaredIn: workspacePath };\n }\n }\n const configured = isPlainObject(document.provider) ? Object.keys(document.provider).sort() : [];\n return { kind: 'unknown', providerId, path, configured };\n}\n\n/**\n * Drop a provider entry's connection keys (`PROVIDER_CONNECTION_KEYS`) from a settings document,\n * on key paths so an id with a dot in it stays one segment. The local half of the `/model` save\n * rule: the workspace gate ignores these keys for a provider the user defines, so a copy left in\n * the local file would only look live.\n */\nexport function deleteProviderConnectionKeys(\n document: Record<string, unknown>,\n providerId: string,\n): void {\n for (const key of PROVIDER_CONNECTION_KEYS) {\n deleteNestedValue(document, ['provider', providerId, key]);\n }\n}\n\nexport type ProviderKeyWriteResult =\n | {\n ok: true;\n /** The user file the key was written to. */\n path: string;\n /** The local file whose same-named entry lost its connection keys, when one did. */\n localCleared?: string;\n /** Why that local entry could not be cleared. The key itself is saved. */\n localError?: string;\n }\n | { ok: false; error: string };\n\n/**\n * Write `apiKey`, exactly as typed (a literal or a reference), to the user file's entry for\n * `providerId`. Refuses an id the user file does not define rather than creating one, and edits\n * nothing else in either file. The write is atomic and validates the whole document, as every\n * settings write does.\n */\nexport function writeProviderKey(\n workspace: string,\n providerId: string,\n apiKey: string,\n): ProviderKeyWriteResult {\n const path = settingsScopePath('user', workspace);\n // Checked before the user file is written, as a `/model` save does: the local half reads the\n // local file, and a save that could only do one half stops with the notice instead.\n const localPath = settingsScopePath('local', workspace);\n const localRefusal = projectFileWriteRefusal(localPath);\n if (localRefusal) return { ok: false, error: `${localPath}: ${localRefusal}` };\n const written = new SettingsRepository(path).update((candidate) => {\n const entry = providerEntryOf(candidate, providerId);\n if (!entry) throw new Error(`Provider \"${providerId}\" is not configured in ${path}.`);\n entry.apiKey = apiKey;\n });\n if (!written.ok) {\n return {\n ok: false,\n error: redactSecrets(formatSettingsDiagnostics(written.diagnostics), [apiKey]),\n };\n }\n\n if (!existsSync(localPath)) return { ok: true, path };\n const local = readSettingsDocument(localPath);\n const localEntry = local.status === 'valid' ? providerEntryOf(local.document, providerId) : null;\n if (!localEntry || !PROVIDER_CONNECTION_KEYS.some((key) => localEntry[key] !== undefined)) {\n return { ok: true, path };\n }\n const cleared = new SettingsRepository(localPath).update((candidate) =>\n deleteProviderConnectionKeys(candidate, providerId),\n );\n return cleared.ok\n ? { ok: true, path, localCleared: localPath }\n : {\n ok: true,\n path,\n localError: redactSecrets(formatSettingsDiagnostics(cleared.diagnostics), [apiKey]),\n };\n}\n\n/**\n * One attempt, and a short per-request limit: a wrong or dead host must fail in seconds, not after\n * the session's ten retries. `maxAttempts` counts retries after the first request, so 0 is one\n * request; a transient refusal is what the sheet's retry and the CLI's `r` are for.\n */\nconst CHECK_RETRIES = 0;\nconst CHECK_REQUEST_TIMEOUT_MS = 20_000;\n\nexport interface ProviderKeyCheckOptions {\n /** The provider whose endpoint is asked. Its own `apiKey` is not used. */\n provider: Pick<ProviderConfig, 'type' | 'baseURL' | 'baseUrl'>;\n /** The new key as typed: a literal, `{env:VAR}` or `{file:path}`. */\n apiKey: string;\n /** Where a relative `{file:…}` reference is rooted. */\n workspace: string;\n retry: RetryConfig;\n discover?: (options: ModelDiscoveryOptions) => Promise<DiscoveredModel[]>;\n signal?: AbortSignal;\n}\n\nexport type ProviderKeyCheckResult =\n { ok: true; modelCount: number } | { ok: false; error: string };\n\n/**\n * Ask the provider's configured endpoint for its model list with the new key. The list itself is\n * discarded: a key update changes the key and nothing else. A provider without a base URL is not\n * asked at all, since anything else would send the key to a host the entry does not name.\n */\nexport async function checkProviderKey(\n options: ProviderKeyCheckOptions,\n): Promise<ProviderKeyCheckResult> {\n const baseUrl = options.provider.baseURL ?? options.provider.baseUrl;\n if (!baseUrl) {\n return {\n ok: false,\n error: 'This provider has no base URL configured, so the key cannot be tested.',\n };\n }\n const resolved = resolveSecret(options.apiKey, options.workspace);\n if (!resolved) {\n const reference = parseSecretReference(options.apiKey);\n return {\n ok: false,\n error:\n reference.kind === 'env'\n ? `Environment variable ${reference.name} is not set.`\n : reference.kind === 'file'\n ? `Could not read a key from ${reference.path}.`\n : 'API key is required.',\n };\n }\n const discover = options.discover ?? discoverModels;\n try {\n const models = await discover({\n type: options.provider.type,\n baseUrl,\n apiKey: resolved,\n retry: {\n ...options.retry,\n maxAttempts: Math.min(options.retry.maxAttempts, CHECK_RETRIES),\n requestTimeoutMs: Math.min(options.retry.requestTimeoutMs, CHECK_REQUEST_TIMEOUT_MS),\n },\n signal: options.signal,\n });\n if (models.length === 0) {\n return { ok: false, error: 'The endpoint did not return any models.' };\n }\n return { ok: true, modelCount: models.length };\n } catch (caught) {\n const message = caught instanceof Error ? caught.message : 'The key check failed.';\n return { ok: false, error: redactSecrets(message, [options.apiKey, resolved]) };\n }\n}\n","import { readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\n/**\n * The Ink release whose renderer Book's TUI was last verified against. A dependency bump changes\n * `package.json` but not this constant, so the contract test and `npm run verify:ink` fail until\n * someone re-verifies the TUI (docs/guide/development.md, \"Upgrading Ink\") and updates it.\n */\nexport const VERIFIED_INK_VERSION = '7.1.1';\n\n/**\n * The incremental renderer's cursor rewind with Ink's trailing-newline fix (upstream issue 909,\n * shipped in Ink 7.0.0). Without it, a frame that ends in a newline rewinds one row short and\n * later updates land below their rows.\n */\nconst INK_TRAILING_NEWLINE_FIX = 'ansiEscapes.cursorUp(previousLines.length - 1)';\n\n/** The subset of a writable terminal stream Ink's renderer writes through. */\nexport interface InkStream {\n isTTY?: boolean;\n rows?: number;\n write: (data: string) => unknown;\n}\n\n/** Ink's private log-update renderer. Ink 7 added `reset()`; every wrapper must forward it. */\nexport interface LogUpdateRenderer {\n (output: string): boolean | void;\n clear: () => void;\n done: () => void;\n reset?: () => void;\n sync: (output: string) => void;\n setCursorPosition: (position: unknown) => void;\n isCursorDirty: () => boolean;\n willRender: (output: string) => boolean;\n}\n\nexport type CreateLogUpdate = (\n stream: InkStream,\n options?: { incremental?: boolean },\n) => LogUpdateRenderer;\n\n/** Ink's build directory, where its private modules live. Throws when Ink cannot be resolved. */\nexport function inkBuildDir(from: string | URL = import.meta.url): string {\n const require = createRequire(from);\n return dirname(require.resolve('ink'));\n}\n\n/**\n * Ink's private log-update module: the renderer the TUI wraps for frame capture and scroll hints,\n * and the file whose trailing-newline fix decides the renderer mode. Throws when Ink cannot be\n * resolved. `from` is the module Ink is resolved from.\n */\nexport function inkLogUpdatePath(from?: string | URL): string {\n return join(inkBuildDir(from), 'log-update.js');\n}\n\n/** The installed Ink version, or undefined when Ink cannot be resolved. `from` is the module Ink is resolved from. */\nexport function installedInkVersion(from?: string | URL): string | undefined {\n try {\n const manifest = JSON.parse(\n readFileSync(join(inkBuildDir(from), '..', 'package.json'), 'utf8'),\n ) as {\n version?: unknown;\n };\n return typeof manifest.version === 'string' ? manifest.version : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Incremental rendering is safe only when Ink's renderer carries the trailing-newline fix. `from` is the module Ink is resolved from. */\nexport function hasInkTrailingNewlineFix(from?: string | URL): boolean {\n try {\n return readFileSync(inkLogUpdatePath(from), 'utf8').includes(INK_TRAILING_NEWLINE_FIX);\n } catch {\n return false;\n }\n}\n\n/** Runs `update` and commits the React work it scheduled before returning. */\nexport type InkFlushSync = (update: () => void) => void;\n\n/**\n * The `flushSync` of Ink's own reconciler (#538), or undefined when Ink's private `reconciler.js`\n * cannot be read. Ink renders with `LegacyRoot`, but React 19's reconciler no longer commits a\n * legacy root's update synchronously outside a batch: a `setState` from a stdout `resize` listener\n * waits for a microtask, after Ink's own `resized()` has drawn the tree from before the resize. A\n * listener that sets the new size inside this flush has it committed before `resized()` draws.\n * Ink exports no flushSync, so this reads the reconciler instance Ink renders with.\n */\nexport async function loadInkFlushSync(from?: string | URL): Promise<InkFlushSync | undefined> {\n try {\n const module = (await import(pathToFileURL(join(inkBuildDir(from), 'reconciler.js')).href)) as {\n default?: { flushSyncFromReconciler?: unknown };\n };\n const reconciler = module.default;\n const flush = reconciler?.flushSyncFromReconciler;\n if (typeof flush !== 'function') return undefined;\n return (update) => {\n flush.call(reconciler, update);\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Replaces Ink's private `logUpdate.create` with `wrap(original)`, so every renderer Ink creates\n * afterwards goes through the wrapper. Returns false, leaving Ink's renderer in place, when Ink's\n * module layout has changed.\n */\nexport async function wrapInkLogUpdate(\n wrap: (createBase: CreateLogUpdate) => CreateLogUpdate,\n): Promise<boolean> {\n try {\n const module = (await import(pathToFileURL(inkLogUpdatePath()).href)) as {\n default: { create: CreateLogUpdate };\n };\n const logUpdate = module.default;\n logUpdate.create = wrap(logUpdate.create);\n return true;\n } catch {\n return false;\n }\n}\n","import {\n wrapInkLogUpdate,\n type CreateLogUpdate,\n type InkStream,\n type LogUpdateRenderer,\n} from './ink-renderer.js';\n\nexport interface TranscriptScrollHint {\n top: number;\n bottom: number;\n delta: number;\n}\n\nlet pendingHint: TranscriptScrollHint | null = null;\nlet rendererInstalled = false;\n\nexport function isInkScrollRendererEnabled(value?: string): boolean {\n return value === 'on';\n}\n\nexport function setTranscriptScrollHint(hint: TranscriptScrollHint): void {\n if (!Number.isFinite(hint.delta) || hint.delta === 0) return;\n if (\n pendingHint &&\n pendingHint.top === hint.top &&\n pendingHint.bottom === hint.bottom &&\n Math.sign(pendingHint.delta) === Math.sign(hint.delta)\n ) {\n pendingHint = { ...pendingHint, delta: pendingHint.delta + hint.delta };\n return;\n }\n pendingHint = { ...hint };\n}\n\nfunction splitFrame(frame: string): string[] {\n return frame.endsWith('\\n') ? frame.slice(0, -1).split('\\n') : frame.split('\\n');\n}\n\nfunction getFrameEndRow(frame: string, visibleLineCount: number): number {\n return visibleLineCount + (frame.endsWith('\\n') ? 1 : 0);\n}\n\nfunction cursorTo(row: number): string {\n return `\\x1b[${Math.max(1, Math.floor(row))};1H`;\n}\n\nfunction setScrollRegion(top: number, bottom: number): string {\n return `\\x1b[${Math.max(1, Math.floor(top))};${Math.max(1, Math.floor(bottom))}r`;\n}\n\nexport function isPureVerticalShift(\n previousLines: readonly string[],\n nextLines: readonly string[],\n hint: TranscriptScrollHint,\n): boolean {\n if (previousLines.length !== nextLines.length) return false;\n const top = Math.floor(hint.top) - 1;\n const bottom = Math.floor(hint.bottom);\n const delta = Math.trunc(hint.delta);\n if (top < 0 || bottom > nextLines.length || bottom - top < 2) return false;\n if (Math.abs(delta) >= bottom - top) return false;\n\n for (let row = 0; row < top; row++) {\n if (previousLines[row] !== nextLines[row]) return false;\n }\n for (let row = bottom; row < nextLines.length; row++) {\n if (previousLines[row] !== nextLines[row]) return false;\n }\n\n if (delta > 0) {\n for (let row = top; row < bottom - delta; row++) {\n if (nextLines[row] !== previousLines[row + delta]) return false;\n }\n } else {\n const distance = Math.abs(delta);\n for (let row = top + distance; row < bottom; row++) {\n if (nextLines[row] !== previousLines[row - distance]) return false;\n }\n }\n return true;\n}\n\nfunction writeExposedRows(\n stream: InkStream,\n lines: readonly string[],\n top: number,\n bottom: number,\n delta: number,\n): void {\n const distance = Math.abs(delta);\n const first = delta > 0 ? bottom - distance : top;\n for (let offset = 0; offset < distance; offset++) {\n const row = first + offset;\n stream.write(cursorTo(row + 1) + (lines[row] ?? '') + '\\x1b[K');\n }\n}\n\nfunction createScrollAwareRenderer(\n stream: InkStream,\n options: { incremental?: boolean } | undefined,\n createBase: CreateLogUpdate,\n): LogUpdateRenderer {\n const base = createBase(stream, options);\n let previousFrame = '';\n\n const render = ((output: string) => {\n const hint = pendingHint;\n pendingHint = null;\n const previousLines = splitFrame(previousFrame);\n const nextLines = splitFrame(output);\n const terminalRows = stream.rows ?? 0;\n const frameEndRow = getFrameEndRow(output, nextLines.length);\n const canScroll =\n Boolean(stream.isTTY) &&\n terminalRows > 0 &&\n frameEndRow <= terminalRows &&\n previousFrame.length > 0 &&\n hint !== null &&\n hint.bottom <= nextLines.length &&\n isPureVerticalShift(previousLines, nextLines, hint);\n\n if (canScroll && hint) {\n const top = Math.floor(hint.top);\n const bottom = Math.floor(hint.bottom);\n const delta = Math.trunc(hint.delta);\n stream.write('\\x1b[?25l' + setScrollRegion(top, bottom));\n stream.write(`\\x1b[${Math.abs(delta)}${delta > 0 ? 'S' : 'T'}`);\n writeExposedRows(stream, nextLines, top - 1, bottom, delta);\n // Ink's sync() positions the input cursor relative to the frame bottom.\n // Re-anchor there first so its cursor suffix cannot repaint the prompt on\n // a transcript row after a terminal-native scroll.\n stream.write(setScrollRegion(1, terminalRows) + cursorTo(frameEndRow));\n previousFrame = output;\n base.sync(output);\n return true;\n }\n\n previousFrame = output;\n return base(output);\n }) as LogUpdateRenderer;\n\n render.clear = () => {\n pendingHint = null;\n previousFrame = '';\n base.clear();\n };\n render.done = () => {\n pendingHint = null;\n previousFrame = '';\n base.done();\n };\n render.reset = () => {\n pendingHint = null;\n previousFrame = '';\n base.reset?.();\n };\n render.sync = (output: string) => {\n pendingHint = null;\n previousFrame = output;\n base.sync(output);\n };\n render.setCursorPosition = (position: unknown) => base.setCursorPosition(position);\n render.isCursorDirty = () => base.isCursorDirty();\n render.willRender = (output: string) => {\n const shouldRender = base.willRender(output);\n if (!shouldRender) pendingHint = null;\n return shouldRender;\n };\n return render;\n}\n\nexport async function installInkScrollRenderer(enabled?: boolean): Promise<void> {\n if (rendererInstalled) return;\n // DECSTBM/CSI S/T rendering remains available for profiling, but Windows\n // ConPTY can corrupt fixed footer rows after a later incremental repaint.\n if (!(enabled ?? isInkScrollRendererEnabled(process.env.BOOK_SCROLL_RENDERER))) return;\n rendererInstalled = true;\n\n // Keep Ink's built-in renderer when its internal module layout changes:\n // wrapInkLogUpdate reports false and leaves Ink's own renderer in place.\n await wrapInkLogUpdate(\n (createBase) => (stream, options) => createScrollAwareRenderer(stream, options, createBase),\n );\n}\n","import {\n wrapInkLogUpdate,\n type CreateLogUpdate,\n type InkStream,\n type LogUpdateRenderer,\n} from './ink-renderer.js';\n\nexport interface FrameCursorPosition {\n x: number;\n y: number;\n}\n\nlet latestFrame = '';\nlet lastCursorPosition: FrameCursorPosition | null = null;\nlet captureInstalled = false;\nconst frameUpdateListeners = new Set<() => void>();\n\nfunction notifyFrameUpdate(): void {\n for (const listener of frameUpdateListeners) {\n try {\n listener();\n } catch {\n // A terminal overlay must never break Ink's renderer.\n }\n }\n}\n\nfunction splitFrame(frame: string): string[] {\n return frame.endsWith('\\n') ? frame.slice(0, -1).split('\\n') : frame.split('\\n');\n}\n\nfunction toCursorPosition(position: unknown): FrameCursorPosition | null {\n if (!position || typeof position !== 'object') return null;\n const candidate = position as { x?: unknown; y?: unknown };\n if (typeof candidate.x !== 'number' || typeof candidate.y !== 'number') return null;\n return { x: candidate.x, y: candidate.y };\n}\n\n/** Lines of the most recently rendered full-screen frame. */\nexport function getFrameLines(): string[] {\n return latestFrame.length === 0 ? [] : splitFrame(latestFrame);\n}\n\n/** Last cursor position Ink asked the renderer to restore, if any. */\nexport function getLastCursorPosition(): FrameCursorPosition | null {\n return lastCursorPosition;\n}\n\n/** Subscribe to completed Ink frame writes, used to preserve terminal overlays. */\nexport function subscribeToFrameUpdates(listener: () => void): () => void {\n frameUpdateListeners.add(listener);\n return () => frameUpdateListeners.delete(listener);\n}\n\n/** Override the captured snapshot. Used by focused selection tests. */\nexport function setFrameSnapshotForTesting(\n frame: string | null,\n cursor: FrameCursorPosition | null = null,\n): void {\n latestFrame = frame ?? '';\n lastCursorPosition = cursor;\n}\n\nexport function createFrameCapturingRenderer(\n stream: InkStream,\n options: { incremental?: boolean } | undefined,\n createBase: CreateLogUpdate,\n): LogUpdateRenderer {\n const base = createBase(stream, options);\n\n const render = ((output: string) => {\n latestFrame = output;\n const rendered = base(output);\n if (rendered !== false) notifyFrameUpdate();\n return rendered;\n }) as LogUpdateRenderer;\n\n render.clear = () => {\n latestFrame = '';\n base.clear();\n };\n render.done = () => {\n latestFrame = '';\n lastCursorPosition = null;\n base.done();\n };\n // Ink 7 resets its renderer's memory of the last frame after handing the terminal to a child.\n render.reset = () => {\n latestFrame = '';\n base.reset?.();\n };\n render.sync = (output: string) => {\n latestFrame = output;\n base.sync(output);\n notifyFrameUpdate();\n };\n render.setCursorPosition = (position: unknown) => {\n lastCursorPosition = toCursorPosition(position);\n base.setCursorPosition(position);\n };\n render.isCursorDirty = () => base.isCursorDirty();\n render.willRender = (output: string) => base.willRender(output);\n return render;\n}\n\n/** Capture Ink's latest full frame so mouse selection can extract visible text. */\nexport async function installFrameCapture(): Promise<void> {\n if (captureInstalled) return;\n captureInstalled = true;\n\n // Keep the TUI usable if Ink changes its private renderer layout: wrapInkLogUpdate reports\n // false and leaves Ink's own renderer in place.\n await wrapInkLogUpdate(\n (createBase) => (stream, options) => createFrameCapturingRenderer(stream, options, createBase),\n );\n}\n","import { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { parseEnvBoolean } from '../env-boolean.js';\nimport { inkBuildDir } from './ink-renderer.js';\n\n/**\n * Caches Ink's output rows across frames, because Ink's frame is where the scroll stall lives.\n *\n * The first scroll through a heavy session stalls 40-250 ms at a time. A CPU profile of a real\n * 4 MB session on Windows puts the stall in Ink, not in Book's components: Ink's private\n * `Output.get` is 29% of it, `renderNodeToOutput` 19%, Yoga layout 14% and the React render 14%,\n * while DiffBlock and the Markdown renderer are 2.5% between them. Ink builds a fresh `Output`,\n * with fresh caches, for every frame, so every frame re-tokenizes every visible line and turns\n * every row's cells back into a string even when the row is the same text it drew one frame\n * earlier, one row higher.\n *\n * The replacement here walks the operations exactly the way Ink's own `get` does, collects the\n * fragments each output row is made of, and hands a row's string to the row cache instead of\n * rebuilding it, so an unchanged row costs a map lookup whether it moved or not. A row's string is\n * built by Ink's original `get` on a scratch `Output` holding only that row's fragments, which is\n * why the frames are byte-identical rather than merely close. Sharing one set of width and\n * styled-character caches across frames is the other half: a line measured once stays measured.\n *\n * Measured on the shipped code, on a quiet box, over the real 4 MB session at 120x40 on Windows and\n * 40 wheel-up reports at 30/s, 7 interleaved runs each: stalls over 40 ms fell from 1.4 to 0.4 per\n * run, their summed time from 73 ms to 18 ms, and the worst stall per run from 54 ms to 35 ms at\n * the median and from 62 ms to 43 ms at the worst. Under load, with a game running, the\n * measurement prototype's figures are the ones that hold: 9.0 to 4.4 stalls per run and 509 ms to\n * 221 ms summed. Frames were byte-identical to Ink's own `get` throughout — 0 mismatches over 315\n * real frames of the built CLI.\n *\n * `installInkOutputCache` refuses to touch Ink unless `isInkOutputShape` accepts it, and\n * `ink-renderer.contract.test.ts` pins that shape and the frames themselves against the installed\n * Ink, so an Ink upgrade that changes `Output` fails the contract tier instead of quietly changing\n * what the TUI draws.\n */\n\n/** Ink's private clip rectangle. A bound is undefined when the clip is not on that axis. */\nexport interface InkClip {\n x1?: number;\n x2?: number;\n y1?: number;\n y2?: number;\n}\n\n/** Ink's per-line text transformer, which receives the line's index within its own write. */\nexport type InkTransformer = (line: string, index: number) => string;\n\n/** Ink's recorded writes and clips, in the order `get` replays them. */\nexport type InkOperation =\n | { type: 'write'; x: number; y: number; text: string; transformers: InkTransformer[] }\n | { type: 'clip'; clip: InkClip | undefined }\n | { type: 'unclip' };\n\n/** The width, styled-character and block-width caches Ink keeps per `Output`, and thus per frame. */\nexport interface InkOutputCaches {\n styledChars: Map<string, unknown>;\n widths: Map<string, number>;\n blockWidths: Map<string, number>;\n getStyledChars: (line: string) => unknown;\n getStringWidth: (text: string) => number;\n getWidestLine: (text: string) => number;\n}\n\n/** What `Output.get` returns: the whole frame, and the number of rows it was built from. */\nexport interface InkOutputFrame {\n output: string;\n height: number;\n}\n\n/** Ink's private `Output`: the operation log `get` replays into a frame of rows. */\nexport interface InkOutput {\n width: number;\n height: number;\n operations: InkOperation[];\n caches: InkOutputCaches;\n write(x: number, y: number, text: string, options: { transformers: InkTransformer[] }): void;\n clip(clip: InkClip): void;\n unclip(): void;\n}\n\n/** Ink's private `Output` class, as `node_modules/ink/build/output.js` exports it. */\nexport interface InkOutputConstructor {\n new (options: { width: number; height: number }): InkOutput;\n prototype: { get: (this: InkOutput) => InkOutputFrame };\n}\n\n/** `slice-ansi`, resolved from Ink's own location so it is the copy Ink measures with. */\nexport type SliceAnsi = (text: string, from: number, to: number) => string;\n\n/**\n * Entry sizes. A key here is a whole line or a whole rendered row, held for the life of the\n * process, so the defaults are kept small: past them the least recently used key is dropped rather\n * than the map being emptied, because an emptied map makes the next frame miss every row.\n */\nexport interface RowCacheLimits {\n styledChars: number;\n widths: number;\n blockWidths: number;\n sliceCache: number;\n rowCache: number;\n}\n\nconst DEFAULT_LIMITS: RowCacheLimits = {\n styledChars: 1024,\n widths: 8192,\n blockWidths: 1024,\n sliceCache: 2048,\n rowCache: 2048,\n};\n\nexport interface RowCachedGetOptions {\n /** Ink's own `get`, which still builds the rows this one cannot serve from the cache. */\n originalGet: (this: InkOutput) => InkOutputFrame;\n sliceAnsi: SliceAnsi;\n Output: InkOutputConstructor;\n limits?: Partial<RowCacheLimits>;\n}\n\n/**\n * How many frames and rows have been rendered, how many of those rows needed no work — a row that\n * came from the cache, and a row nothing was written to, which is its own empty string — how many\n * frames were handed straight back to Ink, and how full Book's own two caches are.\n */\nexport interface OutputCacheStats {\n frames: number;\n rows: number;\n rowHits: number;\n fallbacks: number;\n rowCacheSize: number;\n sliceCacheSize: number;\n}\n\nexport interface RowCachedGet {\n get: (this: InkOutput) => InkOutputFrame;\n stats: () => OutputCacheStats;\n}\n\n/** One write's place and final text on a row, after clipping and transformers. */\ntype InkFragment = [x: number, line: string];\n\n/** Drops the oldest key, which a `Map` yields first because it iterates in insertion order. */\nfunction evictOldest<T>(cache: Map<string, T>): void {\n const oldest = cache.keys().next().value;\n if (oldest !== undefined) cache.delete(oldest);\n}\n\n/**\n * Reads a key and makes it the newest again, so that a row in use on every frame is the last one a\n * full cache drops. A `Map` iterates in insertion order, so re-setting is the whole of recency.\n */\nfunction refresh<T>(cache: Map<string, T>, key: string): T | undefined {\n const value = cache.get(key);\n if (value === undefined) return undefined;\n cache.delete(key);\n cache.set(key, value);\n return value;\n}\n\n/** Stores a value, dropping the oldest key first when the map is already at its limit. */\nfunction remember<T>(cache: Map<string, T>, key: string, value: T, limit: number): void {\n if (cache.size >= limit) evictOldest(cache);\n cache.set(key, value);\n}\n\n/**\n * Drops the oldest keys until the map is at or below `limit`. For Ink's own shared maps, which are\n * filled inside Ink's methods and so cannot be held at a limit as they are written: they are\n * trimmed once at the start of each frame, never emptied, since emptying one would make the frame\n * that follows it re-measure every line — the stall this module exists to remove.\n */\nfunction trim<T>(cache: Map<string, T>, limit: number): void {\n while (cache.size > limit) evictOldest(cache);\n}\n\n/**\n * Builds a replacement for `Output.prototype.get` that keeps Ink's row strings, caches and\n * clipping behaviour and adds a row cache on top. Every cache lives in this factory's closure, so\n * two of these share nothing.\n */\nexport function createRowCachedGet(options: RowCachedGetOptions): RowCachedGet {\n const { originalGet, sliceAnsi, Output } = options;\n const limits: RowCacheLimits = { ...DEFAULT_LIMITS, ...options.limits };\n const rowCache = new Map<string, string>();\n const sliceCache = new Map<string, string>();\n const counters = { frames: 0, rows: 0, rowHits: 0, fallbacks: 0 };\n let sharedCaches: InkOutputCaches | null = null;\n\n const get = function (this: InkOutput): InkOutputFrame {\n // The first Output seen donates its caches; every later frame reuses them, so a line measured\n // once stays measured.\n if (sharedCaches === null) sharedCaches = this.caches;\n const caches = sharedCaches;\n this.caches = caches;\n trim(caches.styledChars, limits.styledChars);\n trim(caches.widths, limits.widths);\n trim(caches.blockWidths, limits.blockWidths);\n\n const rows: InkFragment[][] = [];\n for (let y = 0; y < this.height; y++) rows.push([]);\n const clips: (InkClip | undefined)[] = [];\n for (const operation of this.operations) {\n if (operation.type === 'clip') clips.push(operation.clip);\n if (operation.type === 'unclip') clips.pop();\n if (operation.type !== 'write') continue;\n const { text, transformers } = operation;\n let { x, y } = operation;\n let lines = text.split('\\n');\n const clip = clips.at(-1);\n if (clip) {\n const { x1, x2, y1, y2 } = clip;\n const clipHorizontally = typeof x1 === 'number' && typeof x2 === 'number';\n const clipVertically = typeof y1 === 'number' && typeof y2 === 'number';\n // Text outside the clip altogether is skipped whole, before any measuring.\n if (clipHorizontally) {\n if (x + caches.getWidestLine(text) < x1 || x > x2) continue;\n }\n if (clipVertically) {\n if (y + lines.length < y1 || y > y2) continue;\n }\n if (clipHorizontally) {\n lines = lines.map((line) => {\n const from = x < x1 ? x1 - x : 0;\n const width = caches.getStringWidth(line);\n const to = x + width > x2 ? x2 - x : width;\n const key = `${from},${to},${line.length},${line}`;\n const cached = refresh(sliceCache, key);\n if (cached !== undefined) return cached;\n const sliced = sliceAnsi(line, from, to);\n remember(sliceCache, key, sliced, limits.sliceCache);\n return sliced;\n });\n if (x < x1) x = x1;\n }\n if (clipVertically) {\n const from = y < y1 ? y1 - y : 0;\n const to = y + lines.length > y2 ? y2 - y : lines.length;\n lines = lines.slice(from, to);\n if (y < y1) y = y1;\n }\n }\n let offsetY = 0;\n for (const [index, original] of lines.entries()) {\n // Ink's quirk: a line the output has no row for is dropped without advancing the offset, so\n // every later line of the same write lands on that missing row and is dropped with it.\n // Kept, because the frames must match.\n if (!rows[y + offsetY]) continue;\n let line = original;\n for (const transformer of transformers) line = transformer(line, index);\n // A transformer can hand back a line with a newline in it, which Ink keeps on this one row\n // with the newline a single cell. The scratch Output below splits its writes on newlines,\n // so its rows would come back shifted; this frame is given back to Ink whole, and none of\n // it reaches the row cache.\n if (line.includes('\\n')) {\n counters.fallbacks++;\n counters.frames++;\n return originalGet.call(this);\n }\n rows[y + offsetY].push([x, line]);\n offsetY++;\n }\n }\n\n const frame: string[] = new Array(rows.length);\n const misses: number[] = [];\n const missKeys: string[] = [];\n for (let row = 0; row < rows.length; row++) {\n const fragments = rows[row];\n counters.rows++;\n if (fragments.length === 0) {\n frame[row] = '';\n counters.rowHits++;\n continue;\n }\n // Length-prefixed, so no line's content can make two different rows share a key.\n let key = `${this.width}|`;\n for (const [x, line] of fragments) key += `${x},${line.length},${line}`;\n const cached = refresh(rowCache, key);\n if (cached === undefined) {\n misses.push(row);\n missKeys.push(key);\n } else {\n frame[row] = cached;\n counters.rowHits++;\n }\n }\n if (misses.length > 0) {\n // One scratch Output per frame for the misses, with each missing row's fragments as plain\n // writes: Ink's own get still builds every cell and every style.\n const scratch = new Output({ width: this.width, height: misses.length });\n scratch.caches = caches;\n misses.forEach((row, index) => {\n for (const [x, line] of rows[row] ?? []) {\n scratch.operations.push({ type: 'write', x, y: index, text: line, transformers: [] });\n }\n });\n const built = originalGet.call(scratch).output.split('\\n');\n misses.forEach((row, index) => {\n const text = built[index] ?? '';\n frame[row] = text;\n remember(rowCache, missKeys[index], text, limits.rowCache);\n });\n }\n counters.frames++;\n return { output: frame.join('\\n'), height: this.height };\n };\n\n Object.defineProperty(get, INSTALLED_MARK, { value: true });\n return {\n get,\n stats: () => ({ ...counters, rowCacheSize: rowCache.size, sliceCacheSize: sliceCache.size }),\n };\n}\n\n/** Whether a candidate carries the parts of Ink's `Output` this module reads. */\nexport function isInkOutputShape(candidate: unknown): candidate is InkOutputConstructor {\n if (typeof candidate !== 'function') return false;\n const Output = candidate as InkOutputConstructor;\n const prototype: unknown = Output.prototype;\n if (typeof prototype !== 'object' || prototype === null) return false;\n if (typeof (prototype as { get?: unknown }).get !== 'function') return false;\n let instance: InkOutput;\n try {\n instance = new Output({ width: 1, height: 1 });\n } catch {\n return false;\n }\n if (!Array.isArray(instance.operations)) return false;\n const caches: unknown = instance.caches;\n if (typeof caches !== 'object' || caches === null) return false;\n const shaped = caches as Partial<InkOutputCaches>;\n return (\n typeof shaped.getStyledChars === 'function' &&\n typeof shaped.getStringWidth === 'function' &&\n typeof shaped.getWidestLine === 'function' &&\n shaped.styledChars instanceof Map &&\n shaped.widths instanceof Map &&\n shaped.blockWidths instanceof Map\n );\n}\n\n/**\n * Set on the installed `get` itself, which is where \"is the cache in place\" belongs: a module-level\n * flag would be mutable state the module owns, and would outlive an `Output` that was replaced.\n */\nconst INSTALLED_MARK = Symbol('book.rowCachedInkGet');\n\n/** Whether a candidate class's `Output.prototype.get` is already the row-cached one. */\nfunction isRowCachedGet(candidate: unknown): boolean {\n if (typeof candidate !== 'function') return false;\n const prototype = (candidate as { prototype?: { get?: unknown } }).prototype;\n const get = prototype?.get as { [INSTALLED_MARK]?: unknown } | undefined;\n return get?.[INSTALLED_MARK] === true;\n}\n\n/**\n * Replaces Ink's `Output.prototype.get` with the row-cached one, once per process, and returns\n * whether the cached `get` is installed after this call.\n *\n * Returns true without touching anything when `Output.prototype.get` already carries the mark: the\n * cache is in place, whatever this call's environment says, and a second wrap would be a race\n * against the first. Returns false, leaving Ink alone, when `BOOK_INK_OUTPUT_CACHE` reads as false —\n * `0`, `false`, `off` or `no`, in any case and with spaces around it — and when Ink's private\n * `Output` or the `slice-ansi` copy beside it cannot be read, so an upgrade that changes either\n * costs speed rather than correctness: `ink-renderer.contract.test.ts` fails on that upgrade.\n */\nexport async function installInkOutputCache(\n env: NodeJS.ProcessEnv = process.env,\n): Promise<boolean> {\n try {\n const outputPath = join(inkBuildDir(), 'output.js');\n const outputModule = await import(pathToFileURL(outputPath).href);\n if (isRowCachedGet(outputModule.default)) return true;\n if (parseEnvBoolean(env.BOOK_INK_OUTPUT_CACHE) === false) return false;\n const sliceAnsiPath = createRequire(outputPath).resolve('slice-ansi');\n const sliceAnsi = (await import(pathToFileURL(sliceAnsiPath).href)).default;\n const Output: unknown = outputModule.default;\n if (!isInkOutputShape(Output) || typeof sliceAnsi !== 'function') return false;\n const originalGet = Output.prototype.get;\n Output.prototype.get = createRowCachedGet({\n originalGet,\n sliceAnsi: sliceAnsi as SliceAnsi,\n Output,\n }).get;\n return true;\n } catch {\n return false;\n }\n}\n","import { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { parseEnvBoolean } from '../env-boolean.js';\nimport type { InkClip, InkOutput } from './ink-output-cache.js';\nimport { inkBuildDir } from './ink-renderer.js';\n\n/**\n * Culls Ink's drawing of transcript rows and straddling descendants that lie wholly outside the\n * viewport's clip.\n *\n * The row cache in `ink-output-cache.ts` took the per-frame string building out of Ink's frame, but\n * the walk that visits every mounted node every frame — Ink's private `renderNodeToOutput` — was\n * still 26% of the TUI's busy CPU while scrolling a real session. The virtual transcript keeps\n * about one viewport of rows mounted above and below the view, so most of that walk is spent on\n * rows nobody can see: for each text node it squashes the text, measures `widestLine` and wraps it,\n * then records a write that `Output.get` throws away when it lies outside the clip. Even after\n * culling wholly offscreen rows, rows that straddle the viewport edge (e.g. long diffs or tool\n * outputs) still accounted for 54% of writes landing outside the clip.\n *\n * The walk checks `yogaNode.getDisplay() === Yoga.DISPLAY_NONE` first and returns at once when it\n * is, so the cull installs exactly that: a mounted transcript row's yoga node gets an own\n * `getDisplay` that reports `DISPLAY_NONE` while the row's box lies wholly outside the innermost\n * clip of the walk in progress, and the node's real display otherwise.\n *\n * The registration root is `TranscriptView`'s `contentRef` box, registered once on mount. Because\n * that box always straddles the viewport while there is anything to scroll, propagation registers\n * ChatPanel's column, and the column registers each row during the walk itself, before the walk\n * visits it. Newly mounted rows in a frame are therefore culled in that same frame.\n * When a registered node's `getDisplay` finds the node visible but straddling the innermost vertical\n * clip (its box crosses `y1` or `y2`), it registers each of its child elements (`childNodes` with a\n * `yogaNode`, text nodes included) through the same registrar, skipping the scan when the child\n * count and the first and last child identities are unchanged. Because Ink's walk queries\n * `getDisplay` on a parent before walking its children, straddling children are culled in the exact\n * same walk.\n * Nodes wholly inside the clip register nothing (everything is visible), and culled nodes register\n * nothing.\n *\n * The cull rests on the invariant that a node whose box lies wholly outside the innermost vertical\n * clip can be hidden without changing what is drawn on screen. That holds only if:\n * (a) everything the node's subtree draws lies inside the node's own box, and\n * (b) no descendant draws against a clip of its own (Ink's `Output.get` applies only the\n * innermost clip; it does not intersect nested clips).\n *\n * This invariant is held in two ways:\n * 1. Summary rows truncate, never wrap: the one-row summary boxes in `ToolCallBlock.tsx`,\n * `AgentMessage.tsx` and `QuietToolRun.tsx` pass `wrap=\"truncate-end\"` to every `Text` child,\n * so long targets never spill onto the line below the box.\n * 2. A node that clips itself is never culled: if an element's style clips (`overflow`, `overflowX`\n * or `overflowY` is `'hidden'`), its `getDisplay` returns the real display without culling.\n * Its subtree draws against its own clip, so only drawing it is exact; its children are still\n * decided against that clip when the walk reaches them.\n *\n * A clipping or overflowing descendant added to the transcript later would break exactness.\n * Yoga layout is untouched — Yoga reads display from its native style, not from this JavaScript\n * method — so a culled node is still laid out exactly as before; only its drawing is skipped.\n *\n * Absolute tops are memoized per frame in a `WeakMap` from element to `{ frame, top }` so sibling\n * elements avoid re-summing `getComputedTop()` up the ancestor chain. A frame counter advances\n * whenever `Output.prototype.clip` is called on a different `Output` instance than the previous\n * call (Ink builds a new `Output` for every frame). An element's absolute top is its own computed\n * top plus its parent's memoized absolute top (a missing parent is 0; a link with no readable top\n * leaves the cull undecided). Tops are recomputed when the frame counter advances.\n *\n * The clip state comes from wrapping `Output.prototype.clip` and `unclip`, which the walk calls in\n * walk order: one stack per `Output` (a `WeakMap`) of clips pushed and not yet popped. A frame's\n * stack is detected when an `Output` is first clipped; only that stack array is held during the\n * frame, avoiding retaining the `Output` instance and its operations after the frame completes.\n * Each cull instance uses a single shared `getDisplay` function looking up node state in a `WeakMap`,\n * avoiding per-node closures.\n *\n * Measured on the shipped code, over the real 4 MB session the issue used, at 120x40, resumed in a\n * PTY (40 wheel-up reports at 30/s, then 40 wheel-down at 30/s, then flicks; main's build against\n * this branch's build, interleaved runs): every real frame was drawn twice in the same process,\n * once with the cull and once without it, from the same layout: 0 mismatches over 514 frames\n * (Windows 248, Linux 266). The real TUI's colour screenshots (run-book, 120x40 and 30x30) are\n * byte-identical with the cull on, off, and on main. The cull removes 68% (Windows) and 66% (Linux)\n * of the walk's writes; before it, 73% of the writes the walk made landed wholly outside the clip;\n * with it, 14% (Windows) and 20% (Linux). On Linux, quiet box (6 runs per build): Ink's draw time\n * summed over the wheel-up phase fell from 475 ms to 303 ms (median draw 5.9 ms to 3.3 ms), over the\n * wheel-down phase from 365 ms to 211 ms (4.3 ms to 2.5 ms). Event-loop stalls over 40 ms during\n * wheel-up fell from 1.5 to 0.3 per run (summed 66 ms to 15 ms), and the worst stall per run from\n * 45 ms to 37 ms at the median. On Windows, quiet box (5 runs per build): draw time over wheel-up fell\n * from 483 ms to 361 ms (median draw 6.8 ms to 4.7 ms), over wheel-down from 355 ms to 200 ms\n * (median draw 4.8 ms to 2.7 ms). Stalls over 20 ms during wheel-up fell from 15.2 to 11.8 per run;\n * stalls over 40 ms did not move (about one per run in either build): what remains there is the React\n * mount and Yoga layout of newly mounted heavy rows, which the cull does not touch. On Windows under\n * load (a game started half a minute into an 8-round set; rounds interleaved): stalls over 40 ms\n * during wheel-up fell from 3.6 to 0.9 per run (summed 182 ms to 40 ms), the worst stall per run from\n * 56 ms to 42 ms at the median, and draw time over wheel-up from 538 ms to 358 ms.\n *\n * `installInkRenderCull` refuses to touch Ink unless the prototype carries the clip push, pop and\n * `get` the cull needs, and `ink-renderer.contract.test.ts` draws the same frames with and without\n * the cull against the installed Ink, so an Ink upgrade that changes the walk fails the contract\n * tier instead of quietly changing what the TUI draws. `BOOK_INK_RENDER_CULL=off` turns it off.\n */\n\n/** The yoga-node surface the cull reads, and that Ink's walk itself calls. */\nexport interface CullableYogaNode {\n getDisplay: () => number;\n getComputedTop: () => number;\n getComputedHeight: () => number;\n}\n\n/**\n * An Ink DOM element as the cull reads it: a yoga node, style overflow properties, and the parent\n * chain whose computed tops add up to the walk's absolute y. Ink's `DOMElement` satisfies this.\n */\nexport interface CullableElement {\n yogaNode?: CullableYogaNode | null;\n parentNode?: CullableElement | null;\n childNodes?: readonly CullableElement[] | Iterable<CullableElement> | null;\n style?: {\n overflow?: string;\n overflowX?: string;\n overflowY?: string;\n };\n}\n\n/** The pieces of Ink's `Output` the cull wraps: the walk's clip push and pop, beside its `get`. */\ninterface CullableOutputConstructor {\n prototype: {\n clip: (this: InkOutput, clip: InkClip) => void;\n unclip: (this: InkOutput) => void;\n get: (...args: unknown[]) => unknown;\n };\n}\n\nexport interface InkRenderCullOptions {\n /** Ink's own clip push, which records the clip for `get` to replay. */\n clip: (this: InkOutput, clip: InkClip) => void;\n /** Ink's own clip pop. */\n unclip: (this: InkOutput) => void;\n /** `Yoga.DISPLAY_NONE`, read from the copy of yoga-layout Ink itself resolves. */\n displayNone: number;\n}\n\n/** The wrapped clip and pop, and the row registrar bound to their clip state. */\nexport interface InkRenderCull {\n clip: (this: InkOutput, clip: InkClip) => void;\n unclip: (this: InkOutput) => void;\n cullWhenOffscreen: (element: CullableElement | null) => void;\n}\n\n/** Set on the wrapped `clip` itself, which is where \"is the cull in place\" belongs. */\nconst INSTALLED_MARK = Symbol('book.inkRenderCull');\n\n/** Set on a yoga node to remember its original unculled display method across cull instances. */\nconst REAL_DISPLAY = Symbol('book.inkRenderCullRealDisplay');\n\ninterface MarkedClip {\n [INSTALLED_MARK]?: true;\n}\n\ninterface MarkedYogaNode {\n [REAL_DISPLAY]?: () => number;\n [key: symbol]: unknown;\n}\n\nfunction isCullableYogaNode(candidate: unknown): candidate is CullableYogaNode {\n if (typeof candidate !== 'object' || candidate === null) return false;\n const node = candidate as Partial<CullableYogaNode>;\n return (\n typeof node.getDisplay === 'function' &&\n typeof node.getComputedTop === 'function' &&\n typeof node.getComputedHeight === 'function'\n );\n}\n\n/**\n * Builds the wrapped clip and pop that keep the walk's clip state, and the registrar that gives a\n * row's yoga node an own `getDisplay` deciding against that state at draw time. Every state lives\n * in this factory's closure, so two of these share nothing.\n */\nexport function createInkRenderCull(options: InkRenderCullOptions): InkRenderCull {\n const { clip: originalClip, unclip: originalUnclip, displayNone } = options;\n\n // Registration mark created per cull instance so a new instance re-registers previously marked nodes.\n const cullMark = Symbol('book.inkRenderCullDisplay');\n\n // The clips each Output has pushed and not yet popped.\n const clips = new WeakMap<InkOutput, InkClip[]>();\n // Active stack array for the current frame, keeping no reference to the Output instance itself.\n let activeClipStack: InkClip[] | null = null;\n let frame = 0;\n const absoluteTops = new WeakMap<CullableElement, { frame: number; top: number | undefined }>();\n\n // Tracks the childNodes length, first child, and last child last scanned per element.\n const scannedChildren = new WeakMap<\n CullableElement,\n {\n length: number;\n firstChild: CullableElement | undefined;\n lastChild: CullableElement | undefined;\n }\n >();\n\n // Maps each registered yogaNode to its element and realDisplay method for sharedGetDisplay.\n const nodeRecords = new WeakMap<\n CullableYogaNode,\n { element: CullableElement; realDisplay: () => number }\n >();\n\n const clip = function (this: InkOutput, pushed: InkClip): void {\n let stack = clips.get(this);\n if (stack === undefined) {\n frame++;\n stack = [];\n clips.set(this, stack);\n }\n activeClipStack = stack;\n stack.push(pushed);\n originalClip.call(this, pushed);\n };\n Object.defineProperty(clip, INSTALLED_MARK, { value: true });\n\n const unclip = function (this: InkOutput): void {\n clips.get(this)?.pop();\n originalUnclip.call(this);\n };\n\n const getAbsoluteTop = (node: CullableElement): number | undefined => {\n const cached = absoluteTops.get(node);\n if (cached !== undefined && cached.frame === frame) {\n return cached.top;\n }\n const nodeTop = node.yogaNode?.getComputedTop();\n if (typeof nodeTop !== 'number') {\n absoluteTops.set(node, { frame, top: undefined });\n return undefined;\n }\n let parentTop = 0;\n if (node.parentNode != null) {\n const parentAbsoluteTop = getAbsoluteTop(node.parentNode);\n if (parentAbsoluteTop === undefined) {\n absoluteTops.set(node, { frame, top: undefined });\n return undefined;\n }\n parentTop = parentAbsoluteTop;\n }\n const totalTop = parentTop + nodeTop;\n absoluteTops.set(node, { frame, top: totalTop });\n return totalTop;\n };\n\n function sharedGetDisplay(this: CullableYogaNode): number {\n const record = nodeRecords.get(this);\n if (record === undefined) {\n const stored = (this as unknown as MarkedYogaNode)[REAL_DISPLAY];\n if (typeof stored === 'function') return stored.call(this);\n const proto = Object.getPrototypeOf(this) as CullableYogaNode | null;\n if (typeof proto?.getDisplay === 'function') return proto.getDisplay.call(this);\n return displayNone;\n }\n const { element, realDisplay } = record;\n const display = realDisplay.call(this);\n if (display === displayNone) return display;\n if (activeClipStack === null || activeClipStack.length === 0) return display;\n const innermost = activeClipStack.at(-1);\n if (innermost === undefined) return display;\n const { y1, y2 } = innermost;\n // No clip, or one that clips horizontally only, never decides a cull.\n if (typeof y1 !== 'number' || typeof y2 !== 'number') return display;\n const top = getAbsoluteTop(element);\n if (top === undefined) return display;\n const bottom = top + this.getComputedHeight();\n\n const style = element.style;\n const clipsSelf =\n style !== undefined &&\n (style.overflow === 'hidden' || style.overflowX === 'hidden' || style.overflowY === 'hidden');\n\n const straddles = (top < y1 && bottom > y1) || (top < y2 && bottom > y2);\n if (straddles) {\n const currentChildren = element.childNodes;\n let currentLength = 0;\n let firstChild: CullableElement | undefined;\n let lastChild: CullableElement | undefined;\n if (Array.isArray(currentChildren)) {\n currentLength = currentChildren.length;\n if (currentLength > 0) {\n firstChild = currentChildren[0];\n lastChild = currentChildren[currentLength - 1];\n }\n } else if (\n currentChildren &&\n typeof (currentChildren as Iterable<CullableElement>)[Symbol.iterator] === 'function'\n ) {\n const items = Array.from(currentChildren as Iterable<CullableElement>);\n currentLength = items.length;\n if (currentLength > 0) {\n firstChild = items[0];\n lastChild = items[currentLength - 1];\n }\n }\n\n const lastScanned = scannedChildren.get(element);\n if (\n lastScanned === undefined ||\n lastScanned.length !== currentLength ||\n lastScanned.firstChild !== firstChild ||\n lastScanned.lastChild !== lastChild\n ) {\n scannedChildren.set(element, {\n length: currentLength,\n firstChild,\n lastChild,\n });\n if (\n currentChildren &&\n typeof (currentChildren as Iterable<CullableElement>)[Symbol.iterator] === 'function'\n ) {\n for (const child of currentChildren as Iterable<CullableElement>) {\n cull.cullWhenOffscreen(child);\n }\n }\n }\n }\n\n if (clipsSelf) return display;\n if (bottom <= y1 || top >= y2) return displayNone;\n return display;\n }\n\n const cull: InkRenderCull = {\n clip,\n unclip,\n cullWhenOffscreen: (element: CullableElement | null): void => {\n if (element === null || typeof element !== 'object') return;\n const yogaNode = element.yogaNode;\n if (!isCullableYogaNode(yogaNode)) return;\n const marked = yogaNode as unknown as MarkedYogaNode;\n if (marked[cullMark] === true) return;\n\n let realDisplay = marked[REAL_DISPLAY];\n if (typeof realDisplay !== 'function') {\n realDisplay = yogaNode.getDisplay;\n Object.defineProperty(yogaNode, REAL_DISPLAY, { value: realDisplay, configurable: true });\n }\n\n nodeRecords.set(yogaNode, { element, realDisplay });\n Object.defineProperty(yogaNode, 'getDisplay', {\n value: sharedGetDisplay,\n configurable: true,\n });\n Object.defineProperty(yogaNode, cullMark, { value: true, configurable: true });\n },\n };\n\n return cull;\n}\n\n/** Whether a candidate's prototype already carries the marked clip of an installed cull. */\nfunction isRenderCullInstalled(candidate: unknown): boolean {\n if (typeof candidate !== 'function') return false;\n const prototype = (candidate as { prototype?: { clip?: unknown } }).prototype;\n const clip = prototype?.clip as MarkedClip | undefined;\n return typeof clip === 'function' && clip[INSTALLED_MARK] === true;\n}\n\n/** Whether a candidate class carries the parts of Ink's `Output` the cull wraps. */\nexport function isInkRenderCullShape(candidate: unknown): candidate is CullableOutputConstructor {\n if (typeof candidate !== 'function') return false;\n const prototype: unknown = (candidate as { prototype?: unknown }).prototype;\n if (typeof prototype !== 'object' || prototype === null) return false;\n const { clip, unclip, get } = prototype as Record<string, unknown>;\n return typeof clip === 'function' && typeof unclip === 'function' && typeof get === 'function';\n}\n\n/**\n * The registrar the TUI calls from a mount effect: a no-op until an install succeeds, then\n * bound to the `Output` that install wrapped. It runs at most once per yoga node, so a row that\n * stays mounted needs it only on mount.\n */\nlet activeCull: InkRenderCull['cullWhenOffscreen'] | null = null;\n\n/**\n * Registers an Ink DOM element so its drawing is skipped while it lies wholly outside the\n * walk's innermost clip.\n */\nexport function cullWhenOffscreen(element: CullableElement | null): void {\n activeCull?.(element);\n}\n\n/**\n * Wraps Ink's `Output.prototype.clip` and `unclip` with the clip-tracking cull, once per process,\n * and returns whether the cull is installed after this call.\n *\n * Returns true without touching anything when `Output.prototype.clip` already carries the mark: the\n * cull is in place, whatever this call's environment says. Returns false, leaving Ink alone, when\n * `BOOK_INK_RENDER_CULL` reads as false — `0`, `false`, `off` or `no`, in any case and with spaces\n * around it — and when Ink's private `Output` or the copy of `yoga-layout` beside it cannot be\n * read, so an upgrade that changes either costs speed rather than correctness:\n * `ink-renderer.contract.test.ts` fails on that upgrade.\n */\nexport async function installInkRenderCull(env: NodeJS.ProcessEnv = process.env): Promise<boolean> {\n try {\n const outputPath = join(inkBuildDir(), 'output.js');\n const outputModule = (await import(pathToFileURL(outputPath).href)) as { default: unknown };\n if (isRenderCullInstalled(outputModule.default)) return true;\n if (parseEnvBoolean(env.BOOK_INK_RENDER_CULL) === false) return false;\n // yoga-layout as Ink itself resolves it, so DISPLAY_NONE is the value the walk compares against.\n const yogaPath = createRequire(outputPath).resolve('yoga-layout');\n const yoga = (await import(pathToFileURL(yogaPath).href)) as {\n default?: { DISPLAY_NONE?: unknown };\n };\n const displayNone = yoga.default?.DISPLAY_NONE;\n if (typeof displayNone !== 'number') return false;\n if (!isInkRenderCullShape(outputModule.default)) return false;\n const { prototype } = outputModule.default as CullableOutputConstructor;\n const cull = createInkRenderCull({\n clip: prototype.clip,\n unclip: prototype.unclip,\n displayNone,\n });\n prototype.clip = cull.clip;\n prototype.unclip = cull.unclip;\n activeCull = cull.cullWhenOffscreen;\n return true;\n } catch {\n return false;\n }\n}\n","import { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n isInkOutputShape,\n type InkClip,\n type InkOutput,\n type InkOutputConstructor,\n type InkOutputFrame,\n type SliceAnsi,\n} from './ink-output-cache.js';\nimport { parseEnvBoolean } from '../env-boolean.js';\nimport { inkBuildDir } from './ink-renderer.js';\n\n/**\n * Keeps every row of Ink's frame within the terminal's width (#525).\n *\n * Ink's private `Output.get` builds each row from a grid `this.width` cells wide, but a write that\n * runs past the last cell grows the row instead of being cut, so a frame row can come out wider\n * than the terminal. The terminal autowraps each over-wide row onto a second physical row, so\n * log-update's idea of how tall its frame is falls short by one row per wrap, and its next\n * incremental frame moves the cursor up into the middle of the old one. The rows it thinks\n * unchanged — blank rows, rules, the input bar — are skipped, and the stale wrapped rows stay: two\n * working indicators, extra rules, a rule fragment, a second input bar.\n *\n * Every narrowing resize used to do that for 50 ms: Ink's `resized()` lays the root out at the new\n * `stdout.columns` and draws at once, while Book's components held the old width in React state\n * until a debounce ran, and the input bar's rules and footer are Boxes with that width set\n * explicitly. Since #538 the TUI commits the new size from a `resize` listener that runs before\n * `resized()`, so no frame laid out at the old width should reach Ink any more; the clip stays as a\n * safety net for any row that still would. `BOOK_INK_FRAME_CLIP=off` turns it off, so a run can show\n * that no row comes out wider than the terminal without it.\n *\n * Cutting the rows at `Output.get` covers both renderer modes and every way Ink writes a frame\n * (incremental, standard, and the clear-terminal branch), because all of them write what `get`\n * returns. `this.width` is the root's computed width, which Ink sets from the live\n * `stdout.columns` in `calculateLayout` before every resized frame.\n *\n * Tabs are the other way a row passes the width without a resize. `string-width`, and so Ink's\n * layout and its `getWidestLine`, count a tab as no cells (Ink's grid gives it one), while the\n * terminal moves to the next multiple-of-8 column. A tab-separated table in a tool's output therefore\n * fits in Ink's frame and still autowraps on screen, with the same desync. A row that holds a tab has\n * every tab replaced by spaces up to the next multiple-of-8 visible column, counted ANSI-aware from\n * column 0 of the row, before it is measured and cut; the spaces also overwrite the cells a terminal\n * tab would jump over and leave stale.\n *\n * Rows are not measured unless a write could put them past the width. A write reaches no further\n * than its `x` plus its widest line, by Ink's `getWidestLine`: a write whose text holds no tab is\n * skipped when that stays inside `this.width`, when it is short enough that even two cells per code\n * unit stays inside, or when Ink's own clip already cuts it at an edge inside the width. That rests\n * on transformers adding styling only, which is all Text's transformer does; Book renders no\n * `<Transform>` that adds width. Only the rows a remaining write covers — a tab write, or one that\n * reaches past the width — are measured, and only the ones that really are too wide are cut, with\n * Ink's `slice-ansi`, which keeps styles closed and never splits a wide character (a row cut beside\n * one may come out a cell short). Rows are measured with the `Output`'s own `getStringWidth`, shared\n * across frames while the row cache is on, falling back to Ink's `string-width`.\n *\n * With the row cache on, the widths the check reads are mostly cache hits. With\n * `BOOK_INK_OUTPUT_CACHE=off` every `Output` has fresh caches, so `getWidestLine` measures each\n * long enough write anew on every frame.\n *\n * `installInkFrameClip` wraps whichever `get` is installed — Ink's own or the row-cached one from\n * `ink-output-cache.ts` — so it is independent of `BOOK_INK_OUTPUT_CACHE` and\n * `BOOK_INK_RENDER_CULL`, and has to be installed after the cache. `ink-renderer.contract.test.ts`\n * pins the parts of `Output` it reads.\n */\n\n/** `string-width`, resolved from Ink's own location so it is the copy Ink measures with. */\nexport type StringWidth = (text: string) => number;\n\nexport interface FrameClippedGetOptions {\n /** The `get` being wrapped: Ink's own, or one already wrapping it. */\n get: (this: InkOutput) => InkOutputFrame;\n sliceAnsi: SliceAnsi;\n stringWidth: StringWidth;\n}\n\n/** Set on the installed `get` itself, as the output cache marks its own. */\nconst INSTALLED_MARK = Symbol('book.inkFrameClip');\n\n/** The terminal's tab stops: every eighth column. */\nconst TAB_STOP = 8;\n\n/**\n * Replaces every tab with spaces up to the next tab stop, as the terminal would move the cursor,\n * counting visible columns from the start of the row. `measure` skips ANSI escapes.\n */\nexport function expandTabs(row: string, measure: StringWidth): string {\n const parts = row.split('\\t');\n let expanded = parts[0] ?? '';\n let column = measure(expanded);\n for (let index = 1; index < parts.length; index++) {\n const part = parts[index] ?? '';\n const pad = TAB_STOP - (column % TAB_STOP);\n expanded += ' '.repeat(pad) + part;\n column += pad + measure(part);\n }\n return expanded;\n}\n\n/** Whether Ink's own clip cuts writes horizontally at an edge inside the width. */\nfunction clipsWithinWidth(clip: InkClip | undefined, width: number): boolean {\n return typeof clip?.x1 === 'number' && typeof clip.x2 === 'number' && clip.x2 <= width;\n}\n\n/**\n * The rows a write could put past the width covers, or null when no write could. A write covers at\n * most the rows from its `y` down by its line count: a vertical clip only moves it down and drops\n * lines.\n */\nfunction rowsPastWidth(output: InkOutput): boolean[] | null {\n let rows: boolean[] | null = null;\n const clips: (InkClip | undefined)[] = [];\n for (const operation of output.operations) {\n if (operation.type === 'clip') {\n clips.push(operation.clip);\n continue;\n }\n if (operation.type === 'unclip') {\n clips.pop();\n continue;\n }\n const { x, y, text } = operation;\n // A tab measures as no cells, to Ink's clip as much as to `getWidestLine`, so it always counts.\n if (!text.includes('\\t')) {\n if (clipsWithinWidth(clips.at(-1), output.width)) continue;\n // No grapheme is wider than two cells or shorter than one code unit, so a write whose length\n // is at most half the room left on its row cannot reach past it, and needs no width lookup.\n if (x + 2 * text.length <= output.width) continue;\n if (x + output.caches.getWidestLine(text) <= output.width) continue;\n }\n rows ??= new Array<boolean>(output.height).fill(false);\n let lines = 1;\n for (let index = text.indexOf('\\n'); index !== -1; index = text.indexOf('\\n', index + 1)) {\n lines++;\n }\n const last = Math.min(y + lines, output.height);\n for (let row = Math.max(y, 0); row < last; row++) rows[row] = true;\n }\n return rows;\n}\n\n/** Builds a `get` that returns what `options.get` does, with every row cut to `this.width`. */\nexport function createFrameClippedGet(\n options: FrameClippedGetOptions,\n): (this: InkOutput) => InkOutputFrame {\n const { get, sliceAnsi, stringWidth } = options;\n const clipped = function (this: InkOutput): InkOutputFrame {\n const frame = get.call(this);\n // Read after `get`: the row cache swaps its shared caches in, which already know the widths.\n const candidates = rowsPastWidth(this);\n if (candidates === null) return frame;\n const caches = this.caches as Partial<InkOutput['caches']> | undefined;\n const measure: StringWidth =\n typeof caches?.getStringWidth === 'function'\n ? caches.getStringWidth.bind(caches)\n : stringWidth;\n const width = this.width;\n const rows = frame.output.split('\\n');\n // A transformer can return a line with a newline in it, which Ink keeps on one grid row; the\n // split then yields more rows than the grid has and every later index shifts, so check them all.\n const checkAll = rows.length !== frame.height;\n let changed = false;\n for (let row = 0; row < rows.length; row++) {\n if (!checkAll && !candidates[row]) continue;\n const original = rows[row] ?? '';\n let text = original.includes('\\t') ? expandTabs(original, measure) : original;\n // Ink trims every row's trailing spaces; a cut row keeps that convention.\n if (measure(text) > width) text = sliceAnsi(text, 0, width).trimEnd();\n if (text === original) continue;\n rows[row] = text;\n changed = true;\n }\n return changed ? { output: rows.join('\\n'), height: frame.height } : frame;\n };\n // Keep the marks of the `get` this wraps, so its installer still sees itself installed and a\n // second install pass adds no layer of either.\n for (const symbol of Object.getOwnPropertySymbols(get)) {\n const descriptor = Object.getOwnPropertyDescriptor(get, symbol);\n if (descriptor) Object.defineProperty(clipped, symbol, descriptor);\n }\n Object.defineProperty(clipped, INSTALLED_MARK, { value: true });\n return clipped;\n}\n\n/** Whether `Output.prototype.get` is already a frame-clipped one. */\nfunction isFrameClippedGet(Output: unknown): boolean {\n if (typeof Output !== 'function') return false;\n const prototype = (Output as { prototype?: { get?: unknown } }).prototype;\n const get = prototype?.get as { [INSTALLED_MARK]?: unknown } | undefined;\n return get?.[INSTALLED_MARK] === true;\n}\n\n/** Whether Ink's `Output` carries the `width`, operations and widest-line cache the clip reads. */\nfunction isClippableOutput(Output: unknown): Output is InkOutputConstructor {\n if (!isInkOutputShape(Output)) return false;\n try {\n return new Output({ width: 3, height: 1 }).width === 3;\n } catch {\n return false;\n }\n}\n\n/**\n * Wraps Ink's `Output.prototype.get` so no frame row is wider than the terminal, once per process,\n * and returns whether the clip is installed after this call. Install it after\n * `installInkOutputCache`, so it wraps the row-cached `get` rather than being wrapped by it. Returns\n * false, leaving Ink alone, when `BOOK_INK_FRAME_CLIP` reads as false — `0`, `false`, `off` or\n * `no`, in any case and with spaces around it — and when Ink's private `Output` or the `slice-ansi`\n * and `string-width` copies beside it cannot be read. A clip already in place stays, whatever this\n * call's environment says.\n */\nexport async function installInkFrameClip(env: NodeJS.ProcessEnv = process.env): Promise<boolean> {\n try {\n const outputPath = join(inkBuildDir(), 'output.js');\n const outputModule = await import(pathToFileURL(outputPath).href);\n const Output: unknown = outputModule.default;\n if (isFrameClippedGet(Output)) return true;\n if (parseEnvBoolean(env.BOOK_INK_FRAME_CLIP) === false) return false;\n const require = createRequire(outputPath);\n const [sliceAnsiModule, stringWidthModule] = await Promise.all([\n import(pathToFileURL(require.resolve('slice-ansi')).href),\n import(pathToFileURL(require.resolve('string-width')).href),\n ]);\n const sliceAnsi: unknown = sliceAnsiModule.default;\n const stringWidth: unknown = stringWidthModule.default;\n if (!isClippableOutput(Output)) return false;\n if (typeof sliceAnsi !== 'function' || typeof stringWidth !== 'function') return false;\n Output.prototype.get = createFrameClippedGet({\n get: Output.prototype.get,\n sliceAnsi: sliceAnsi as SliceAnsi,\n stringWidth: stringWidth as StringWidth,\n });\n return true;\n } catch {\n return false;\n }\n}\n","export type TuiRendererMode = 'safe' | 'incremental' | 'experimental-scroll';\n\ninterface TuiRendererContext {\n isTTY?: boolean;\n screenReader?: boolean;\n incrementalRendererFixed?: boolean;\n platform?: NodeJS.Platform;\n}\n\nexport function resolveTuiRendererMode(\n value = process.env.BOOK_TUI_RENDERER,\n context: TuiRendererContext = {},\n): TuiRendererMode {\n if (context.isTTY === false || context.screenReader) return 'safe';\n if (context.incrementalRendererFixed === false) return 'safe';\n if (value === 'incremental') return 'incremental';\n if (value === 'experimental-scroll') return 'experimental-scroll';\n if (value === undefined) return context.platform === 'win32' ? 'safe' : 'incremental';\n return 'safe';\n}\n\n/**\n * Ink 6 rendered live frames unless it detected CI; Ink 7 also requires a TTY stdout. Book runs its\n * TUI on a non-TTY stdout when a Windows build is launched from WSL, so it passes Ink 6's rule to\n * `render({ interactive })` itself. Same test as Ink's `is-in-ci`.\n */\nexport function isCiEnvironment(env: NodeJS.ProcessEnv = process.env): boolean {\n const set = (key: string) => key in env && env[key] !== '0' && env[key] !== 'false';\n return set('CI') || set('CONTINUOUS_INTEGRATION');\n}\n\n/**\n * The frame cap handed to Ink's `render({ maxFps })`. Ink throttles renders to\n * `ceil(1000 / maxFps)` ms, and on Windows libuv timers fire only on the 15.6 ms system tick:\n * the 17 ms that 60 fps asks for waits two ticks (31 ms) and caps the TUI at about 32 fps, so a\n * transcript scrolled with the wheel or a trackpad moves in visible steps. 72 fps asks for\n * 14 ms, which one tick serves. Elsewhere timers are precise and 60 fps means 60.\n */\nexport function resolveInkMaxFps(platform: NodeJS.Platform = process.platform): number {\n return platform === 'win32' ? 72 : 60;\n}\n"],"mappings":"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","names":["require","splitFrame","createRequire","join","pathToFileURL","join","pathToFileURL","createRequire","createRequire","join","pathToFileURL","INSTALLED_MARK","join","pathToFileURL","createRequire","createRequire","join","pathToFileURL","INSTALLED_MARK","join","pathToFileURL","require","createRequire"]}
@@ -0,0 +1,127 @@
1
+ // src/unc-path.ts
2
+ import { lstatSync, readdir, readlinkSync, realpath } from "fs";
3
+ import { isAbsolute, resolve, win32 } from "path";
4
+ var MAX_LINK_HOPS = 40;
5
+ function isUncSpelling(path) {
6
+ return /^[\\/]{2}/.test(path);
7
+ }
8
+ function components(path) {
9
+ const { root } = win32.parse(path);
10
+ return {
11
+ root,
12
+ names: path.slice(root.length).split(/[\\/]+/).filter(Boolean)
13
+ };
14
+ }
15
+ function reachesUncPath(path, probe = {}) {
16
+ return createUncGuard(probe)(path);
17
+ }
18
+ function isWithinWindowsPath(root, path) {
19
+ const rel = win32.relative(root, path);
20
+ return rel === "" || rel !== ".." && !rel.startsWith("..\\") && !win32.isAbsolute(rel);
21
+ }
22
+ function createUncGuard(probe = {}, options = {}) {
23
+ const trusted = (options.trusted ?? []).map((root) => win32.resolve(root));
24
+ const chosen = (path) => trusted.some((root) => isWithinWindowsPath(root, path));
25
+ const given = (path) => trusted.some((root) => isWithinWindowsPath(path, root));
26
+ const refused = (spelled, resolved) => isUncSpelling(spelled) && !chosen(resolved);
27
+ const lstat = probe.lstat ?? ((entry) => lstatSync(entry));
28
+ const readlink = probe.readlink ?? ((entry) => readlinkSync(entry));
29
+ const probed = /* @__PURE__ */ new Map();
30
+ const look = (entry) => {
31
+ const known = probed.get(entry);
32
+ if (known) return known;
33
+ let found;
34
+ try {
35
+ if (lstat(entry).isSymbolicLink()) {
36
+ let target;
37
+ try {
38
+ target = readlink(entry);
39
+ } catch {
40
+ target = void 0;
41
+ }
42
+ found = { kind: "link", target };
43
+ } else {
44
+ found = { kind: "plain" };
45
+ }
46
+ } catch {
47
+ found = { kind: "missing" };
48
+ }
49
+ probed.set(entry, found);
50
+ return found;
51
+ };
52
+ return (path) => {
53
+ if ((probe.platform ?? process.platform) !== "win32") return false;
54
+ if (refused(path, win32.resolve(path))) return true;
55
+ let { root: current, names } = components(win32.resolve(path));
56
+ let hops = 0;
57
+ while (names.length > 0) {
58
+ const next = win32.join(current, names.shift());
59
+ if (given(next)) {
60
+ current = next;
61
+ continue;
62
+ }
63
+ const entry = look(next);
64
+ if (entry.kind === "missing") return false;
65
+ if (entry.kind === "plain") {
66
+ current = next;
67
+ continue;
68
+ }
69
+ if (++hops > MAX_LINK_HOPS) return true;
70
+ const target = entry.target;
71
+ if (target === void 0) return true;
72
+ const resolved = win32.resolve(current, target);
73
+ if (refused(target, resolved) || refused(resolved, resolved)) return true;
74
+ const walk = components(resolved);
75
+ current = walk.root;
76
+ names = [...walk.names, ...names];
77
+ }
78
+ return false;
79
+ };
80
+ }
81
+ function uncGuardedWalkFs(root, reachesUnc, base = {}, onRefused) {
82
+ if (process.platform !== "win32") return void 0;
83
+ const keyOf = (path) => {
84
+ const asked = String(path);
85
+ return win32.resolve(isAbsolute(asked) ? asked : resolve(root, asked));
86
+ };
87
+ const checked = /* @__PURE__ */ new Set();
88
+ const refuse = (path, rest) => {
89
+ onRefused?.(String(path));
90
+ const callback = rest[rest.length - 1];
91
+ const error = Object.assign(
92
+ new Error(`Not opened, it reaches a UNC location: ${String(path)}`),
93
+ {
94
+ code: "EUNC"
95
+ }
96
+ );
97
+ process.nextTick(callback, error);
98
+ };
99
+ const readdirBase = base.readdir ?? readdir;
100
+ const realpathBase = base.realpath ?? realpath;
101
+ const readdirGuarded = ((path, ...rest) => {
102
+ const key = keyOf(path);
103
+ if (!checked.has(win32.dirname(key)) && !checked.has(key) && reachesUnc(key)) {
104
+ refuse(path, rest);
105
+ return;
106
+ }
107
+ checked.add(key);
108
+ readdirBase(path, ...rest);
109
+ });
110
+ const realpathGuarded = ((path, ...rest) => {
111
+ const key = keyOf(path);
112
+ if (reachesUnc(key)) {
113
+ refuse(path, rest);
114
+ return;
115
+ }
116
+ checked.add(key);
117
+ realpathBase(path, ...rest);
118
+ });
119
+ return { readdir: readdirGuarded, realpath: realpathGuarded };
120
+ }
121
+
122
+ export {
123
+ reachesUncPath,
124
+ createUncGuard,
125
+ uncGuardedWalkFs
126
+ };
127
+ //# sourceMappingURL=chunk-EEX6QNXV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/unc-path.ts"],"sourcesContent":["import { lstatSync, readdir, readlinkSync, realpath } from 'fs';\nimport { isAbsolute, resolve, win32 } from 'path';\nimport type { FileSystemAdapter } from 'tinyglobby';\n\n/**\n * The UNC guard every containment check asks before it resolves a path (#567).\n *\n * On Windows, resolving a path that is, or reaches through a link, `\\\\host\\share\\…` opens that\n * share: `realpath` can send an SMB request, and with it the user's NTLM credentials, to `host`\n * before the path is ever compared with a root. A repository can commit such a link\n * (`docs/x.md -> \\\\host\\share\\y.md`), so the answer has to come from reading each link, never from\n * following one: each component of the path is `lstat`ed, and a link's target is read with\n * `readlink` and walked in its place. Nothing here exists on other platforms, where a path that\n * starts with two slashes is an ordinary local path.\n */\n\n/** The file-system calls the walk makes, injectable so a Windows walk can run on any host. */\nexport interface UncProbe {\n platform?: NodeJS.Platform;\n lstat?: (path: string) => { isSymbolicLink(): boolean };\n readlink?: (path: string) => string;\n}\n\n/** Links followed before the walk gives up and refuses: a cycle, or a chain no file needs. */\nconst MAX_LINK_HOPS = 40;\n\n/**\n * A path that starts with two separators: `\\\\host\\share`, `//host/share`, and the `\\\\?\\UNC\\…` and\n * `\\\\.\\…` namespaces. All of them are refused, not only the ones naming a host, since a namespace\n * prefix can name one too.\n */\nexport function isUncSpelling(path: string): boolean {\n return /^[\\\\/]{2}/.test(path);\n}\n\nfunction components(path: string): { root: string; names: string[] } {\n const { root } = win32.parse(path);\n return {\n root,\n names: path\n .slice(root.length)\n .split(/[\\\\/]+/)\n .filter(Boolean),\n };\n}\n\n/**\n * Whether resolving `path` on Windows could reach a UNC location: the path is spelled as one, or a\n * link on the way to it (the path itself included) points at one. `true` is also the answer for a\n * link that cannot be read and for a chain longer than any file needs, so a caller that refuses on\n * `true` fails closed. A component that does not exist ends the walk: nothing past it can be\n * resolved, so nothing past it is reached. Always `false` off Windows.\n *\n * Each call probes a path at most once (see {@link createUncGuard}); a caller asking about many\n * paths makes one guard and asks it instead.\n */\nexport function reachesUncPath(path: string, probe: UncProbe = {}): boolean {\n return createUncGuard(probe)(path);\n}\n\n/** {@link reachesUncPath} for one top-level call, answering from what it already probed. */\nexport type UncGuard = (path: string) => boolean;\n\n/** What probing one path found: a link carries its target, `undefined` when it cannot be read. */\ntype Probed =\n { kind: 'missing' } | { kind: 'plain' } | { kind: 'link'; target: string | undefined };\n\n/** What a {@link createUncGuard} takes as given. */\nexport interface UncGuardOptions {\n /**\n * Roots the user chose, such as an `additionalDirectories` entry or the memory directory. Each\n * is taken as given, on a share or not, with the directories above it: only what lies below is\n * walked. A link there to a UNC location outside every such root is judged as any other.\n */\n trusted?: readonly string[];\n}\n\n/** Whether `path` is `root` or lies below it, both as Windows reads them. */\nfunction isWithinWindowsPath(root: string, path: string): boolean {\n const rel = win32.relative(root, path);\n return rel === '' || (rel !== '..' && !rel.startsWith('..\\\\') && !win32.isAbsolute(rel));\n}\n\n/**\n * A {@link reachesUncPath} that remembers what it probed (#579). Paths that share a prefix are\n * walked through the same components, and a link to an absolute target restarts the walk at the\n * volume root, so the same `lstat` was asked again and again; here each path is probed once, with\n * its link read once. The memo lives as long as the guard, so a caller makes one per top-level\n * call (a listing, a walk) and drops it after: a file system that changes later is probed afresh.\n * With `trusted` roots ({@link UncGuardOptions}) a location inside one of them is not a refusal.\n */\nexport function createUncGuard(probe: UncProbe = {}, options: UncGuardOptions = {}): UncGuard {\n const trusted = (options.trusted ?? []).map((root) => win32.resolve(root));\n // A UNC location the user chose: one inside a trusted root.\n const chosen = (path: string) => trusted.some((root) => isWithinWindowsPath(root, path));\n // A trusted root, or a directory above one: taken as given, never probed.\n const given = (path: string) => trusted.some((root) => isWithinWindowsPath(path, root));\n const refused = (spelled: string, resolved: string) =>\n isUncSpelling(spelled) && !chosen(resolved);\n const lstat = probe.lstat ?? ((entry: string) => lstatSync(entry));\n const readlink = probe.readlink ?? ((entry: string) => readlinkSync(entry));\n const probed = new Map<string, Probed>();\n const look = (entry: string): Probed => {\n const known = probed.get(entry);\n if (known) return known;\n let found: Probed;\n try {\n if (lstat(entry).isSymbolicLink()) {\n let target: string | undefined;\n try {\n target = readlink(entry);\n } catch {\n target = undefined;\n }\n found = { kind: 'link', target };\n } else {\n found = { kind: 'plain' };\n }\n } catch {\n found = { kind: 'missing' };\n }\n probed.set(entry, found);\n return found;\n };\n\n return (path) => {\n if ((probe.platform ?? process.platform) !== 'win32') return false;\n if (refused(path, win32.resolve(path))) return true;\n let { root: current, names } = components(win32.resolve(path));\n let hops = 0;\n while (names.length > 0) {\n const next = win32.join(current, names.shift()!);\n if (given(next)) {\n current = next;\n continue;\n }\n const entry = look(next);\n if (entry.kind === 'missing') return false;\n if (entry.kind === 'plain') {\n current = next;\n continue;\n }\n if (++hops > MAX_LINK_HOPS) return true;\n const target = entry.target;\n if (target === undefined) return true;\n // A relative target is relative to the directory holding the link (a junction's is always\n // absolute), and Windows takes `..` in it by name, as `resolve` does.\n const resolved = win32.resolve(current, target);\n if (refused(target, resolved) || refused(resolved, resolved)) return true;\n const walk = components(resolved);\n current = walk.root;\n names = [...walk.names, ...names];\n }\n return false;\n };\n}\n\ntype Callback = (error: NodeJS.ErrnoException | null, ...results: unknown[]) => void;\n\n/** The calls {@link uncGuardedWalkFs} wraps; each defaults to `fs`'s own. */\nexport type WalkFsBase = Pick<FileSystemAdapter, 'readdir' | 'realpath'>;\n\n/**\n * The `fs` a tinyglobby walk under `root` is handed on Windows so that it never opens a UNC\n * location (#579), or `undefined` elsewhere, where the guard is always `false`.\n *\n * tinyglobby follows links: fdir `realpath`s each link entry, then `stat`s and lists what that\n * resolved to. It also picks where to start from the pattern, so a pattern can name a share as\n * the directory to list. Two calls therefore ask `reachesUnc` first and, when the answer is yes,\n * fail the way an unreadable entry does, which fdir passes over:\n *\n * - `realpath`, for every link entry;\n * - `readdir`, for a directory the walk did not reach by listing its parent. A directory entry is\n * never a link (Windows reports every reparse point as one), so a directory listed from a\n * checked one is local and needs no probe. What is left to check is where the walk starts and\n * the targets of the links it follows.\n *\n * `stat` only ever sees what an approved `realpath` resolved to, so it is not wrapped. Calls that\n * pass go to `base` (a recording adapter, say) or to `fs`; `onRefused` hears of each one that\n * did not, so a walk can say its answer is partial. A path the walk hands over relative is taken\n * from `root`.\n */\nexport function uncGuardedWalkFs(\n root: string,\n reachesUnc: UncGuard,\n base: WalkFsBase = {},\n onRefused?: (path: string) => void,\n): FileSystemAdapter | undefined {\n if (process.platform !== 'win32') return undefined;\n const keyOf = (path: unknown) => {\n const asked = String(path);\n return win32.resolve(isAbsolute(asked) ? asked : resolve(root, asked));\n };\n // The directories this walk listed, and the links it followed: what is under them is local.\n const checked = new Set<string>();\n const refuse = (path: unknown, rest: unknown[]): void => {\n onRefused?.(String(path));\n const callback = rest[rest.length - 1] as Callback;\n const error = Object.assign(\n new Error(`Not opened, it reaches a UNC location: ${String(path)}`),\n {\n code: 'EUNC',\n },\n );\n // Asynchronously, as `fs` answers: the walk counts its pending calls around this one.\n process.nextTick(callback, error);\n };\n const readdirBase = (base.readdir ?? readdir) as (...args: unknown[]) => void;\n const realpathBase = (base.realpath ?? realpath) as (...args: unknown[]) => void;\n const readdirGuarded = ((path: unknown, ...rest: unknown[]): void => {\n const key = keyOf(path);\n if (!checked.has(win32.dirname(key)) && !checked.has(key) && reachesUnc(key)) {\n refuse(path, rest);\n return;\n }\n checked.add(key);\n readdirBase(path, ...rest);\n }) as typeof readdir;\n const realpathGuarded = ((path: unknown, ...rest: unknown[]): void => {\n const key = keyOf(path);\n if (reachesUnc(key)) {\n refuse(path, rest);\n return;\n }\n // fdir lists a linked directory's entries under the link's own spelling.\n checked.add(key);\n realpathBase(path, ...rest);\n }) as typeof realpath;\n return { readdir: readdirGuarded, realpath: realpathGuarded };\n}\n"],"mappings":"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","names":[]}