@selesai/code 0.1.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 (884) hide show
  1. package/README.md +198 -0
  2. package/dist/agents/architect.md +216 -0
  3. package/dist/agents/builder.md +119 -0
  4. package/dist/agents/commentator.md +128 -0
  5. package/dist/agents/explorer.md +51 -0
  6. package/dist/agents/recapper.md +24 -0
  7. package/dist/bun/cli.d.ts +3 -0
  8. package/dist/bun/cli.d.ts.map +1 -0
  9. package/dist/bun/cli.js +9 -0
  10. package/dist/bun/cli.js.map +1 -0
  11. package/dist/bun/register-bedrock.d.ts +2 -0
  12. package/dist/bun/register-bedrock.d.ts.map +1 -0
  13. package/dist/bun/register-bedrock.js +4 -0
  14. package/dist/bun/register-bedrock.js.map +1 -0
  15. package/dist/bun/restore-sandbox-env.d.ts +17 -0
  16. package/dist/bun/restore-sandbox-env.d.ts.map +1 -0
  17. package/dist/bun/restore-sandbox-env.js +36 -0
  18. package/dist/bun/restore-sandbox-env.js.map +1 -0
  19. package/dist/cli/args.d.ts +57 -0
  20. package/dist/cli/args.d.ts.map +1 -0
  21. package/dist/cli/args.js +379 -0
  22. package/dist/cli/args.js.map +1 -0
  23. package/dist/cli/config-selector.d.ts +14 -0
  24. package/dist/cli/config-selector.d.ts.map +1 -0
  25. package/dist/cli/config-selector.js +31 -0
  26. package/dist/cli/config-selector.js.map +1 -0
  27. package/dist/cli/file-processor.d.ts +15 -0
  28. package/dist/cli/file-processor.d.ts.map +1 -0
  29. package/dist/cli/file-processor.js +82 -0
  30. package/dist/cli/file-processor.js.map +1 -0
  31. package/dist/cli/initial-message.d.ts +18 -0
  32. package/dist/cli/initial-message.d.ts.map +1 -0
  33. package/dist/cli/initial-message.js +22 -0
  34. package/dist/cli/initial-message.js.map +1 -0
  35. package/dist/cli/list-models.d.ts +9 -0
  36. package/dist/cli/list-models.d.ts.map +1 -0
  37. package/dist/cli/list-models.js +98 -0
  38. package/dist/cli/list-models.js.map +1 -0
  39. package/dist/cli/project-trust.d.ts +10 -0
  40. package/dist/cli/project-trust.d.ts.map +1 -0
  41. package/dist/cli/project-trust.js +48 -0
  42. package/dist/cli/project-trust.js.map +1 -0
  43. package/dist/cli/session-picker.d.ts +10 -0
  44. package/dist/cli/session-picker.d.ts.map +1 -0
  45. package/dist/cli/session-picker.js +36 -0
  46. package/dist/cli/session-picker.js.map +1 -0
  47. package/dist/cli/startup-ui.d.ts +23 -0
  48. package/dist/cli/startup-ui.d.ts.map +1 -0
  49. package/dist/cli/startup-ui.js +172 -0
  50. package/dist/cli/startup-ui.js.map +1 -0
  51. package/dist/cli.d.ts +3 -0
  52. package/dist/cli.d.ts.map +1 -0
  53. package/dist/cli.js +18 -0
  54. package/dist/cli.js.map +1 -0
  55. package/dist/config.d.ts +154 -0
  56. package/dist/config.d.ts.map +1 -0
  57. package/dist/config.js +579 -0
  58. package/dist/config.js.map +1 -0
  59. package/dist/core/agent-session-runtime.d.ts +119 -0
  60. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  61. package/dist/core/agent-session-runtime.js +303 -0
  62. package/dist/core/agent-session-runtime.js.map +1 -0
  63. package/dist/core/agent-session-services.d.ts +88 -0
  64. package/dist/core/agent-session-services.d.ts.map +1 -0
  65. package/dist/core/agent-session-services.js +119 -0
  66. package/dist/core/agent-session-services.js.map +1 -0
  67. package/dist/core/agent-session.d.ts +607 -0
  68. package/dist/core/agent-session.d.ts.map +1 -0
  69. package/dist/core/agent-session.js +2552 -0
  70. package/dist/core/agent-session.js.map +1 -0
  71. package/dist/core/agents.d.ts +53 -0
  72. package/dist/core/agents.d.ts.map +1 -0
  73. package/dist/core/agents.js +238 -0
  74. package/dist/core/agents.js.map +1 -0
  75. package/dist/core/auth-guidance.d.ts +5 -0
  76. package/dist/core/auth-guidance.d.ts.map +1 -0
  77. package/dist/core/auth-guidance.js +21 -0
  78. package/dist/core/auth-guidance.js.map +1 -0
  79. package/dist/core/auth-storage.d.ts +140 -0
  80. package/dist/core/auth-storage.d.ts.map +1 -0
  81. package/dist/core/auth-storage.js +434 -0
  82. package/dist/core/auth-storage.js.map +1 -0
  83. package/dist/core/bash-executor.d.ts +32 -0
  84. package/dist/core/bash-executor.d.ts.map +1 -0
  85. package/dist/core/bash-executor.js +111 -0
  86. package/dist/core/bash-executor.js.map +1 -0
  87. package/dist/core/compaction/branch-summarization.d.ts +92 -0
  88. package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
  89. package/dist/core/compaction/branch-summarization.js +249 -0
  90. package/dist/core/compaction/branch-summarization.js.map +1 -0
  91. package/dist/core/compaction/compaction.d.ts +122 -0
  92. package/dist/core/compaction/compaction.d.ts.map +1 -0
  93. package/dist/core/compaction/compaction.js +625 -0
  94. package/dist/core/compaction/compaction.js.map +1 -0
  95. package/dist/core/compaction/index.d.ts +7 -0
  96. package/dist/core/compaction/index.d.ts.map +1 -0
  97. package/dist/core/compaction/index.js +7 -0
  98. package/dist/core/compaction/index.js.map +1 -0
  99. package/dist/core/compaction/utils.d.ts +38 -0
  100. package/dist/core/compaction/utils.d.ts.map +1 -0
  101. package/dist/core/compaction/utils.js +153 -0
  102. package/dist/core/compaction/utils.js.map +1 -0
  103. package/dist/core/defaults.d.ts +3 -0
  104. package/dist/core/defaults.d.ts.map +1 -0
  105. package/dist/core/defaults.js +2 -0
  106. package/dist/core/defaults.js.map +1 -0
  107. package/dist/core/diagnostics.d.ts +15 -0
  108. package/dist/core/diagnostics.d.ts.map +1 -0
  109. package/dist/core/diagnostics.js +2 -0
  110. package/dist/core/diagnostics.js.map +1 -0
  111. package/dist/core/event-bus.d.ts +9 -0
  112. package/dist/core/event-bus.d.ts.map +1 -0
  113. package/dist/core/event-bus.js +25 -0
  114. package/dist/core/event-bus.js.map +1 -0
  115. package/dist/core/exec.d.ts +29 -0
  116. package/dist/core/exec.d.ts.map +1 -0
  117. package/dist/core/exec.js +75 -0
  118. package/dist/core/exec.js.map +1 -0
  119. package/dist/core/experimental.d.ts +2 -0
  120. package/dist/core/experimental.d.ts.map +1 -0
  121. package/dist/core/experimental.js +4 -0
  122. package/dist/core/experimental.js.map +1 -0
  123. package/dist/core/export-html/ansi-to-html.d.ts +22 -0
  124. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  125. package/dist/core/export-html/ansi-to-html.js +249 -0
  126. package/dist/core/export-html/ansi-to-html.js.map +1 -0
  127. package/dist/core/export-html/index.d.ts +37 -0
  128. package/dist/core/export-html/index.d.ts.map +1 -0
  129. package/dist/core/export-html/index.js +226 -0
  130. package/dist/core/export-html/index.js.map +1 -0
  131. package/dist/core/export-html/template.css +1066 -0
  132. package/dist/core/export-html/template.html +55 -0
  133. package/dist/core/export-html/template.js +1864 -0
  134. package/dist/core/export-html/tool-renderer.d.ts +34 -0
  135. package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  136. package/dist/core/export-html/tool-renderer.js +108 -0
  137. package/dist/core/export-html/tool-renderer.js.map +1 -0
  138. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  139. package/dist/core/export-html/vendor/marked.min.js +78 -0
  140. package/dist/core/extensions/index.d.ts +12 -0
  141. package/dist/core/extensions/index.d.ts.map +1 -0
  142. package/dist/core/extensions/index.js +9 -0
  143. package/dist/core/extensions/index.js.map +1 -0
  144. package/dist/core/extensions/loader.d.ts +23 -0
  145. package/dist/core/extensions/loader.d.ts.map +1 -0
  146. package/dist/core/extensions/loader.js +531 -0
  147. package/dist/core/extensions/loader.js.map +1 -0
  148. package/dist/core/extensions/runner.d.ts +166 -0
  149. package/dist/core/extensions/runner.d.ts.map +1 -0
  150. package/dist/core/extensions/runner.js +876 -0
  151. package/dist/core/extensions/runner.js.map +1 -0
  152. package/dist/core/extensions/types.d.ts +1209 -0
  153. package/dist/core/extensions/types.d.ts.map +1 -0
  154. package/dist/core/extensions/types.js +45 -0
  155. package/dist/core/extensions/types.js.map +1 -0
  156. package/dist/core/extensions/wrapper.d.ts +20 -0
  157. package/dist/core/extensions/wrapper.d.ts.map +1 -0
  158. package/dist/core/extensions/wrapper.js +22 -0
  159. package/dist/core/extensions/wrapper.js.map +1 -0
  160. package/dist/core/footer-data-provider.d.ts +54 -0
  161. package/dist/core/footer-data-provider.d.ts.map +1 -0
  162. package/dist/core/footer-data-provider.js +338 -0
  163. package/dist/core/footer-data-provider.js.map +1 -0
  164. package/dist/core/http-dispatcher.d.ts +22 -0
  165. package/dist/core/http-dispatcher.d.ts.map +1 -0
  166. package/dist/core/http-dispatcher.js +64 -0
  167. package/dist/core/http-dispatcher.js.map +1 -0
  168. package/dist/core/index.d.ts +13 -0
  169. package/dist/core/index.d.ts.map +1 -0
  170. package/dist/core/index.js +13 -0
  171. package/dist/core/index.js.map +1 -0
  172. package/dist/core/keybindings.d.ts +353 -0
  173. package/dist/core/keybindings.d.ts.map +1 -0
  174. package/dist/core/keybindings.js +295 -0
  175. package/dist/core/keybindings.js.map +1 -0
  176. package/dist/core/messages.d.ts +77 -0
  177. package/dist/core/messages.d.ts.map +1 -0
  178. package/dist/core/messages.js +123 -0
  179. package/dist/core/messages.js.map +1 -0
  180. package/dist/core/model-registry.d.ts +151 -0
  181. package/dist/core/model-registry.d.ts.map +1 -0
  182. package/dist/core/model-registry.js +750 -0
  183. package/dist/core/model-registry.js.map +1 -0
  184. package/dist/core/model-resolver.d.ts +111 -0
  185. package/dist/core/model-resolver.d.ts.map +1 -0
  186. package/dist/core/model-resolver.js +534 -0
  187. package/dist/core/model-resolver.js.map +1 -0
  188. package/dist/core/output-guard.d.ts +7 -0
  189. package/dist/core/output-guard.d.ts.map +1 -0
  190. package/dist/core/output-guard.js +89 -0
  191. package/dist/core/output-guard.js.map +1 -0
  192. package/dist/core/package-manager.d.ts +207 -0
  193. package/dist/core/package-manager.d.ts.map +1 -0
  194. package/dist/core/package-manager.js +2088 -0
  195. package/dist/core/package-manager.js.map +1 -0
  196. package/dist/core/project-trust.d.ts +15 -0
  197. package/dist/core/project-trust.d.ts.map +1 -0
  198. package/dist/core/project-trust.js +59 -0
  199. package/dist/core/project-trust.js.map +1 -0
  200. package/dist/core/prompt-templates.d.ts +53 -0
  201. package/dist/core/prompt-templates.d.ts.map +1 -0
  202. package/dist/core/prompt-templates.js +236 -0
  203. package/dist/core/prompt-templates.js.map +1 -0
  204. package/dist/core/provider-attribution.d.ts +4 -0
  205. package/dist/core/provider-attribution.d.ts.map +1 -0
  206. package/dist/core/provider-attribution.js +82 -0
  207. package/dist/core/provider-attribution.js.map +1 -0
  208. package/dist/core/provider-display-names.d.ts +2 -0
  209. package/dist/core/provider-display-names.d.ts.map +1 -0
  210. package/dist/core/provider-display-names.js +36 -0
  211. package/dist/core/provider-display-names.js.map +1 -0
  212. package/dist/core/resolve-config-value.d.ts +30 -0
  213. package/dist/core/resolve-config-value.d.ts.map +1 -0
  214. package/dist/core/resolve-config-value.js +247 -0
  215. package/dist/core/resolve-config-value.js.map +1 -0
  216. package/dist/core/resource-loader.d.ts +230 -0
  217. package/dist/core/resource-loader.d.ts.map +1 -0
  218. package/dist/core/resource-loader.js +861 -0
  219. package/dist/core/resource-loader.js.map +1 -0
  220. package/dist/core/sdk.d.ts +109 -0
  221. package/dist/core/sdk.d.ts.map +1 -0
  222. package/dist/core/sdk.js +267 -0
  223. package/dist/core/sdk.js.map +1 -0
  224. package/dist/core/session-cwd.d.ts +19 -0
  225. package/dist/core/session-cwd.d.ts.map +1 -0
  226. package/dist/core/session-cwd.js +38 -0
  227. package/dist/core/session-cwd.js.map +1 -0
  228. package/dist/core/session-manager.d.ts +332 -0
  229. package/dist/core/session-manager.d.ts.map +1 -0
  230. package/dist/core/session-manager.js +1230 -0
  231. package/dist/core/session-manager.js.map +1 -0
  232. package/dist/core/settings-manager.d.ts +286 -0
  233. package/dist/core/settings-manager.d.ts.map +1 -0
  234. package/dist/core/settings-manager.js +874 -0
  235. package/dist/core/settings-manager.js.map +1 -0
  236. package/dist/core/skills.d.ts +69 -0
  237. package/dist/core/skills.d.ts.map +1 -0
  238. package/dist/core/skills.js +387 -0
  239. package/dist/core/skills.js.map +1 -0
  240. package/dist/core/slash-commands.d.ts +14 -0
  241. package/dist/core/slash-commands.d.ts.map +1 -0
  242. package/dist/core/slash-commands.js +26 -0
  243. package/dist/core/slash-commands.js.map +1 -0
  244. package/dist/core/source-info.d.ts +18 -0
  245. package/dist/core/source-info.d.ts.map +1 -0
  246. package/dist/core/source-info.js +19 -0
  247. package/dist/core/source-info.js.map +1 -0
  248. package/dist/core/system-prompt.d.ts +31 -0
  249. package/dist/core/system-prompt.d.ts.map +1 -0
  250. package/dist/core/system-prompt.js +128 -0
  251. package/dist/core/system-prompt.js.map +1 -0
  252. package/dist/core/telemetry.d.ts +3 -0
  253. package/dist/core/telemetry.d.ts.map +1 -0
  254. package/dist/core/telemetry.js +9 -0
  255. package/dist/core/telemetry.js.map +1 -0
  256. package/dist/core/timings.d.ts +8 -0
  257. package/dist/core/timings.d.ts.map +1 -0
  258. package/dist/core/timings.js +31 -0
  259. package/dist/core/timings.js.map +1 -0
  260. package/dist/core/tools/bash.d.ts +68 -0
  261. package/dist/core/tools/bash.d.ts.map +1 -0
  262. package/dist/core/tools/bash.js +346 -0
  263. package/dist/core/tools/bash.js.map +1 -0
  264. package/dist/core/tools/edit-diff.d.ts +106 -0
  265. package/dist/core/tools/edit-diff.d.ts.map +1 -0
  266. package/dist/core/tools/edit-diff.js +424 -0
  267. package/dist/core/tools/edit-diff.js.map +1 -0
  268. package/dist/core/tools/edit.d.ts +51 -0
  269. package/dist/core/tools/edit.d.ts.map +1 -0
  270. package/dist/core/tools/edit.js +284 -0
  271. package/dist/core/tools/edit.js.map +1 -0
  272. package/dist/core/tools/file-mutation-queue.d.ts +6 -0
  273. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
  274. package/dist/core/tools/file-mutation-queue.js +52 -0
  275. package/dist/core/tools/file-mutation-queue.js.map +1 -0
  276. package/dist/core/tools/find.d.ts +35 -0
  277. package/dist/core/tools/find.d.ts.map +1 -0
  278. package/dist/core/tools/find.js +305 -0
  279. package/dist/core/tools/find.js.map +1 -0
  280. package/dist/core/tools/grep.d.ts +37 -0
  281. package/dist/core/tools/grep.d.ts.map +1 -0
  282. package/dist/core/tools/grep.js +304 -0
  283. package/dist/core/tools/grep.js.map +1 -0
  284. package/dist/core/tools/index.d.ts +40 -0
  285. package/dist/core/tools/index.d.ts.map +1 -0
  286. package/dist/core/tools/index.js +112 -0
  287. package/dist/core/tools/index.js.map +1 -0
  288. package/dist/core/tools/ls.d.ts +37 -0
  289. package/dist/core/tools/ls.d.ts.map +1 -0
  290. package/dist/core/tools/ls.js +167 -0
  291. package/dist/core/tools/ls.js.map +1 -0
  292. package/dist/core/tools/output-accumulator.d.ts +52 -0
  293. package/dist/core/tools/output-accumulator.d.ts.map +1 -0
  294. package/dist/core/tools/output-accumulator.js +184 -0
  295. package/dist/core/tools/output-accumulator.js.map +1 -0
  296. package/dist/core/tools/path-utils.d.ts +10 -0
  297. package/dist/core/tools/path-utils.d.ts.map +1 -0
  298. package/dist/core/tools/path-utils.js +99 -0
  299. package/dist/core/tools/path-utils.js.map +1 -0
  300. package/dist/core/tools/read.d.ts +35 -0
  301. package/dist/core/tools/read.d.ts.map +1 -0
  302. package/dist/core/tools/read.js +289 -0
  303. package/dist/core/tools/read.js.map +1 -0
  304. package/dist/core/tools/render-utils.d.ts +24 -0
  305. package/dist/core/tools/render-utils.d.ts.map +1 -0
  306. package/dist/core/tools/render-utils.js +65 -0
  307. package/dist/core/tools/render-utils.js.map +1 -0
  308. package/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
  309. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -0
  310. package/dist/core/tools/tool-definition-wrapper.js +34 -0
  311. package/dist/core/tools/tool-definition-wrapper.js.map +1 -0
  312. package/dist/core/tools/truncate.d.ts +70 -0
  313. package/dist/core/tools/truncate.d.ts.map +1 -0
  314. package/dist/core/tools/truncate.js +215 -0
  315. package/dist/core/tools/truncate.js.map +1 -0
  316. package/dist/core/tools/write.d.ts +26 -0
  317. package/dist/core/tools/write.d.ts.map +1 -0
  318. package/dist/core/tools/write.js +197 -0
  319. package/dist/core/tools/write.js.map +1 -0
  320. package/dist/core/trust-manager.d.ts +36 -0
  321. package/dist/core/trust-manager.d.ts.map +1 -0
  322. package/dist/core/trust-manager.js +202 -0
  323. package/dist/core/trust-manager.js.map +1 -0
  324. package/dist/defaults/models.json +3 -0
  325. package/dist/defaults/settings.json +68 -0
  326. package/dist/extensions/copy-turn.ts +125 -0
  327. package/dist/extensions/gitignore-guard.ts +132 -0
  328. package/dist/extensions/hooks/claude-codex-hooks.json +44 -0
  329. package/dist/extensions/hooks/copilot-hooks.json +21 -0
  330. package/dist/extensions/hooks/ponytail-activate.js +91 -0
  331. package/dist/extensions/hooks/ponytail-config.js +122 -0
  332. package/dist/extensions/hooks/ponytail-instructions.js +94 -0
  333. package/dist/extensions/hooks/ponytail-mode-tracker.js +55 -0
  334. package/dist/extensions/hooks/ponytail-runtime.js +68 -0
  335. package/dist/extensions/hooks/ponytail-statusline.ps1 +21 -0
  336. package/dist/extensions/hooks/ponytail-statusline.sh +12 -0
  337. package/dist/extensions/hooks/ponytail-subagent.js +22 -0
  338. package/dist/extensions/package.json +19 -0
  339. package/dist/extensions/pi-extension/index.js +189 -0
  340. package/dist/extensions/pi-extension/package.json +8 -0
  341. package/dist/extensions/pi-extension/test/extension.test.js +167 -0
  342. package/dist/extensions/pi-extension/test/helpers.test.js +92 -0
  343. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +516 -0
  344. package/dist/extensions/pi-powerline-footer/README.md +382 -0
  345. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  346. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +556 -0
  347. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +397 -0
  348. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +151 -0
  349. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +286 -0
  350. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +108 -0
  351. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +59 -0
  352. package/dist/extensions/pi-powerline-footer/colors.ts +69 -0
  353. package/dist/extensions/pi-powerline-footer/context-usage.ts +41 -0
  354. package/dist/extensions/pi-powerline-footer/fixed-editor/cluster.ts +113 -0
  355. package/dist/extensions/pi-powerline-footer/fixed-editor/terminal-split.ts +1077 -0
  356. package/dist/extensions/pi-powerline-footer/git-status.ts +212 -0
  357. package/dist/extensions/pi-powerline-footer/icons.ts +181 -0
  358. package/dist/extensions/pi-powerline-footer/index.ts +2817 -0
  359. package/dist/extensions/pi-powerline-footer/package.json +46 -0
  360. package/dist/extensions/pi-powerline-footer/powerline-config.ts +182 -0
  361. package/dist/extensions/pi-powerline-footer/presets.ts +121 -0
  362. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +24 -0
  363. package/dist/extensions/pi-powerline-footer/segments.ts +566 -0
  364. package/dist/extensions/pi-powerline-footer/separators.ts +57 -0
  365. package/dist/extensions/pi-powerline-footer/shortcuts.ts +47 -0
  366. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +1503 -0
  367. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +38 -0
  368. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +135 -0
  369. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +180 -0
  370. package/dist/extensions/pi-powerline-footer/tests/fixed-editor.test.ts +1416 -0
  371. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +213 -0
  372. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +32 -0
  373. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +61 -0
  374. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +226 -0
  375. package/dist/extensions/pi-powerline-footer/theme.example.json +24 -0
  376. package/dist/extensions/pi-powerline-footer/theme.json +12 -0
  377. package/dist/extensions/pi-powerline-footer/theme.ts +227 -0
  378. package/dist/extensions/pi-powerline-footer/types.ts +191 -0
  379. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +34 -0
  380. package/dist/extensions/pi-powerline-footer/welcome.ts +611 -0
  381. package/dist/extensions/pi-powerline-footer/working-vibes.ts +695 -0
  382. package/dist/extensions/prototype.ts +713 -0
  383. package/dist/extensions/question.ts +350 -0
  384. package/dist/extensions/rtk.ts +81 -0
  385. package/dist/extensions/tps-tracker.ts +280 -0
  386. package/dist/extensions/undo.ts +292 -0
  387. package/dist/index.d.ts +33 -0
  388. package/dist/index.d.ts.map +1 -0
  389. package/dist/index.js +46 -0
  390. package/dist/index.js.map +1 -0
  391. package/dist/main.d.ts +12 -0
  392. package/dist/main.d.ts.map +1 -0
  393. package/dist/main.js +700 -0
  394. package/dist/main.js.map +1 -0
  395. package/dist/migrations.d.ts +33 -0
  396. package/dist/migrations.d.ts.map +1 -0
  397. package/dist/migrations.js +281 -0
  398. package/dist/migrations.js.map +1 -0
  399. package/dist/modes/index.d.ts +9 -0
  400. package/dist/modes/index.d.ts.map +1 -0
  401. package/dist/modes/index.js +8 -0
  402. package/dist/modes/index.js.map +1 -0
  403. package/dist/modes/interactive/assets/clankolas.png +0 -0
  404. package/dist/modes/interactive/components/armin.d.ts +34 -0
  405. package/dist/modes/interactive/components/armin.d.ts.map +1 -0
  406. package/dist/modes/interactive/components/armin.js +333 -0
  407. package/dist/modes/interactive/components/armin.js.map +1 -0
  408. package/dist/modes/interactive/components/assistant-message.d.ts +20 -0
  409. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
  410. package/dist/modes/interactive/components/assistant-message.js +121 -0
  411. package/dist/modes/interactive/components/assistant-message.js.map +1 -0
  412. package/dist/modes/interactive/components/bash-execution.d.ts +34 -0
  413. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
  414. package/dist/modes/interactive/components/bash-execution.js +175 -0
  415. package/dist/modes/interactive/components/bash-execution.js.map +1 -0
  416. package/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
  417. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
  418. package/dist/modes/interactive/components/bordered-loader.js +54 -0
  419. package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
  420. package/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  421. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
  422. package/dist/modes/interactive/components/branch-summary-message.js +44 -0
  423. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
  424. package/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  425. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
  426. package/dist/modes/interactive/components/compaction-summary-message.js +45 -0
  427. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
  428. package/dist/modes/interactive/components/config-selector.d.ts +71 -0
  429. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
  430. package/dist/modes/interactive/components/config-selector.js +506 -0
  431. package/dist/modes/interactive/components/config-selector.js.map +1 -0
  432. package/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  433. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -0
  434. package/dist/modes/interactive/components/countdown-timer.js +33 -0
  435. package/dist/modes/interactive/components/countdown-timer.js.map +1 -0
  436. package/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  437. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
  438. package/dist/modes/interactive/components/custom-editor.js +70 -0
  439. package/dist/modes/interactive/components/custom-editor.js.map +1 -0
  440. package/dist/modes/interactive/components/custom-message.d.ts +20 -0
  441. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
  442. package/dist/modes/interactive/components/custom-message.js +79 -0
  443. package/dist/modes/interactive/components/custom-message.js.map +1 -0
  444. package/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  445. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -0
  446. package/dist/modes/interactive/components/daxnuts.js +140 -0
  447. package/dist/modes/interactive/components/daxnuts.js.map +1 -0
  448. package/dist/modes/interactive/components/diff.d.ts +12 -0
  449. package/dist/modes/interactive/components/diff.d.ts.map +1 -0
  450. package/dist/modes/interactive/components/diff.js +133 -0
  451. package/dist/modes/interactive/components/diff.js.map +1 -0
  452. package/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  453. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
  454. package/dist/modes/interactive/components/dynamic-border.js +21 -0
  455. package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
  456. package/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
  457. package/dist/modes/interactive/components/earendil-announcement.d.ts.map +1 -0
  458. package/dist/modes/interactive/components/earendil-announcement.js +40 -0
  459. package/dist/modes/interactive/components/earendil-announcement.js.map +1 -0
  460. package/dist/modes/interactive/components/extension-editor.d.ts +20 -0
  461. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
  462. package/dist/modes/interactive/components/extension-editor.js +119 -0
  463. package/dist/modes/interactive/components/extension-editor.js.map +1 -0
  464. package/dist/modes/interactive/components/extension-input.d.ts +23 -0
  465. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
  466. package/dist/modes/interactive/components/extension-input.js +61 -0
  467. package/dist/modes/interactive/components/extension-input.js.map +1 -0
  468. package/dist/modes/interactive/components/extension-selector.d.ts +26 -0
  469. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
  470. package/dist/modes/interactive/components/extension-selector.js +83 -0
  471. package/dist/modes/interactive/components/extension-selector.js.map +1 -0
  472. package/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
  473. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -0
  474. package/dist/modes/interactive/components/first-time-setup.js +103 -0
  475. package/dist/modes/interactive/components/first-time-setup.js.map +1 -0
  476. package/dist/modes/interactive/components/footer.d.ts +28 -0
  477. package/dist/modes/interactive/components/footer.d.ts.map +1 -0
  478. package/dist/modes/interactive/components/footer.js +221 -0
  479. package/dist/modes/interactive/components/footer.js.map +1 -0
  480. package/dist/modes/interactive/components/index.d.ts +34 -0
  481. package/dist/modes/interactive/components/index.d.ts.map +1 -0
  482. package/dist/modes/interactive/components/index.js +35 -0
  483. package/dist/modes/interactive/components/index.js.map +1 -0
  484. package/dist/modes/interactive/components/keybinding-hints.d.ts +13 -0
  485. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -0
  486. package/dist/modes/interactive/components/keybinding-hints.js +36 -0
  487. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -0
  488. package/dist/modes/interactive/components/login-dialog.d.ts +52 -0
  489. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -0
  490. package/dist/modes/interactive/components/login-dialog.js +179 -0
  491. package/dist/modes/interactive/components/login-dialog.js.map +1 -0
  492. package/dist/modes/interactive/components/model-selector.d.ts +47 -0
  493. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -0
  494. package/dist/modes/interactive/components/model-selector.js +279 -0
  495. package/dist/modes/interactive/components/model-selector.js.map +1 -0
  496. package/dist/modes/interactive/components/oauth-selector.d.ts +31 -0
  497. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -0
  498. package/dist/modes/interactive/components/oauth-selector.js +165 -0
  499. package/dist/modes/interactive/components/oauth-selector.js.map +1 -0
  500. package/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
  501. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -0
  502. package/dist/modes/interactive/components/scoped-models-selector.js +293 -0
  503. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -0
  504. package/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  505. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -0
  506. package/dist/modes/interactive/components/session-selector-search.js +155 -0
  507. package/dist/modes/interactive/components/session-selector-search.js.map +1 -0
  508. package/dist/modes/interactive/components/session-selector.d.ts +95 -0
  509. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
  510. package/dist/modes/interactive/components/session-selector.js +867 -0
  511. package/dist/modes/interactive/components/session-selector.js.map +1 -0
  512. package/dist/modes/interactive/components/settings-selector.d.ts +73 -0
  513. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -0
  514. package/dist/modes/interactive/components/settings-selector.js +570 -0
  515. package/dist/modes/interactive/components/settings-selector.js.map +1 -0
  516. package/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  517. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -0
  518. package/dist/modes/interactive/components/show-images-selector.js +39 -0
  519. package/dist/modes/interactive/components/show-images-selector.js.map +1 -0
  520. package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  521. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
  522. package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
  523. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
  524. package/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  525. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
  526. package/dist/modes/interactive/components/theme-selector.js +50 -0
  527. package/dist/modes/interactive/components/theme-selector.js.map +1 -0
  528. package/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  529. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
  530. package/dist/modes/interactive/components/thinking-selector.js +51 -0
  531. package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
  532. package/dist/modes/interactive/components/tool-execution.d.ts +63 -0
  533. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
  534. package/dist/modes/interactive/components/tool-execution.js +317 -0
  535. package/dist/modes/interactive/components/tool-execution.js.map +1 -0
  536. package/dist/modes/interactive/components/tree-selector.d.ts +89 -0
  537. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
  538. package/dist/modes/interactive/components/tree-selector.js +1208 -0
  539. package/dist/modes/interactive/components/tree-selector.js.map +1 -0
  540. package/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  541. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -0
  542. package/dist/modes/interactive/components/trust-selector.js +91 -0
  543. package/dist/modes/interactive/components/trust-selector.js.map +1 -0
  544. package/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  545. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
  546. package/dist/modes/interactive/components/user-message-selector.js +114 -0
  547. package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
  548. package/dist/modes/interactive/components/user-message.d.ts +10 -0
  549. package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
  550. package/dist/modes/interactive/components/user-message.js +29 -0
  551. package/dist/modes/interactive/components/user-message.js.map +1 -0
  552. package/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  553. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -0
  554. package/dist/modes/interactive/components/visual-truncate.js +33 -0
  555. package/dist/modes/interactive/components/visual-truncate.js.map +1 -0
  556. package/dist/modes/interactive/interactive-mode.d.ts +381 -0
  557. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
  558. package/dist/modes/interactive/interactive-mode.js +4802 -0
  559. package/dist/modes/interactive/interactive-mode.js.map +1 -0
  560. package/dist/modes/interactive/model-search.d.ts +12 -0
  561. package/dist/modes/interactive/model-search.d.ts.map +1 -0
  562. package/dist/modes/interactive/model-search.js +15 -0
  563. package/dist/modes/interactive/model-search.js.map +1 -0
  564. package/dist/modes/interactive/theme/dark.json +86 -0
  565. package/dist/modes/interactive/theme/light.json +85 -0
  566. package/dist/modes/interactive/theme/theme-controller.d.ts +29 -0
  567. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -0
  568. package/dist/modes/interactive/theme/theme-controller.js +102 -0
  569. package/dist/modes/interactive/theme/theme-controller.js.map +1 -0
  570. package/dist/modes/interactive/theme/theme-schema.json +336 -0
  571. package/dist/modes/interactive/theme/theme.d.ts +119 -0
  572. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  573. package/dist/modes/interactive/theme/theme.js +1056 -0
  574. package/dist/modes/interactive/theme/theme.js.map +1 -0
  575. package/dist/modes/print-mode.d.ts +28 -0
  576. package/dist/modes/print-mode.d.ts.map +1 -0
  577. package/dist/modes/print-mode.js +132 -0
  578. package/dist/modes/print-mode.js.map +1 -0
  579. package/dist/modes/rpc/jsonl.d.ts +17 -0
  580. package/dist/modes/rpc/jsonl.d.ts.map +1 -0
  581. package/dist/modes/rpc/jsonl.js +49 -0
  582. package/dist/modes/rpc/jsonl.js.map +1 -0
  583. package/dist/modes/rpc/rpc-client.d.ts +227 -0
  584. package/dist/modes/rpc/rpc-client.d.ts.map +1 -0
  585. package/dist/modes/rpc/rpc-client.js +467 -0
  586. package/dist/modes/rpc/rpc-client.js.map +1 -0
  587. package/dist/modes/rpc/rpc-mode.d.ts +20 -0
  588. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
  589. package/dist/modes/rpc/rpc-mode.js +637 -0
  590. package/dist/modes/rpc/rpc-mode.js.map +1 -0
  591. package/dist/modes/rpc/rpc-types.d.ts +428 -0
  592. package/dist/modes/rpc/rpc-types.d.ts.map +1 -0
  593. package/dist/modes/rpc/rpc-types.js +8 -0
  594. package/dist/modes/rpc/rpc-types.js.map +1 -0
  595. package/dist/package-manager-cli.d.ts +8 -0
  596. package/dist/package-manager-cli.d.ts.map +1 -0
  597. package/dist/package-manager-cli.js +659 -0
  598. package/dist/package-manager-cli.js.map +1 -0
  599. package/dist/skills/grill-me/SKILL.md +10 -0
  600. package/dist/skills/handoff/SKILL.md +15 -0
  601. package/dist/skills/implanger/SKILL.md +68 -0
  602. package/dist/skills/improve-codebase/REFERENCE.md +78 -0
  603. package/dist/skills/improve-codebase/SKILL.md +178 -0
  604. package/dist/skills/planger/SKILL.md +165 -0
  605. package/dist/skills/ponytail/SKILL.md +117 -0
  606. package/dist/skills/ponytail-audit/SKILL.md +41 -0
  607. package/dist/skills/ponytail-debt/SKILL.md +44 -0
  608. package/dist/skills/ponytail-gain/SKILL.md +50 -0
  609. package/dist/skills/ponytail-help/SKILL.md +69 -0
  610. package/dist/skills/ponytail-review/SKILL.md +57 -0
  611. package/dist/skills/selesai-default/SKILL.md +16 -0
  612. package/dist/themes/powerline-footer/theme.json +33 -0
  613. package/dist/utils/ansi.d.ts +2 -0
  614. package/dist/utils/ansi.d.ts.map +1 -0
  615. package/dist/utils/ansi.js +52 -0
  616. package/dist/utils/ansi.js.map +1 -0
  617. package/dist/utils/changelog.d.ts +22 -0
  618. package/dist/utils/changelog.d.ts.map +1 -0
  619. package/dist/utils/changelog.js +165 -0
  620. package/dist/utils/changelog.js.map +1 -0
  621. package/dist/utils/child-process.d.ts +18 -0
  622. package/dist/utils/child-process.d.ts.map +1 -0
  623. package/dist/utils/child-process.js +106 -0
  624. package/dist/utils/child-process.js.map +1 -0
  625. package/dist/utils/clipboard-image.d.ts +11 -0
  626. package/dist/utils/clipboard-image.d.ts.map +1 -0
  627. package/dist/utils/clipboard-image.js +245 -0
  628. package/dist/utils/clipboard-image.js.map +1 -0
  629. package/dist/utils/clipboard-native.d.ts +10 -0
  630. package/dist/utils/clipboard-native.d.ts.map +1 -0
  631. package/dist/utils/clipboard-native.js +20 -0
  632. package/dist/utils/clipboard-native.js.map +1 -0
  633. package/dist/utils/clipboard.d.ts +2 -0
  634. package/dist/utils/clipboard.d.ts.map +1 -0
  635. package/dist/utils/clipboard.js +117 -0
  636. package/dist/utils/clipboard.js.map +1 -0
  637. package/dist/utils/deprecation.d.ts +4 -0
  638. package/dist/utils/deprecation.d.ts.map +1 -0
  639. package/dist/utils/deprecation.js +13 -0
  640. package/dist/utils/deprecation.js.map +1 -0
  641. package/dist/utils/exif-orientation.d.ts +5 -0
  642. package/dist/utils/exif-orientation.d.ts.map +1 -0
  643. package/dist/utils/exif-orientation.js +158 -0
  644. package/dist/utils/exif-orientation.js.map +1 -0
  645. package/dist/utils/frontmatter.d.ts +8 -0
  646. package/dist/utils/frontmatter.d.ts.map +1 -0
  647. package/dist/utils/frontmatter.js +26 -0
  648. package/dist/utils/frontmatter.js.map +1 -0
  649. package/dist/utils/fs-watch.d.ts +5 -0
  650. package/dist/utils/fs-watch.d.ts.map +1 -0
  651. package/dist/utils/fs-watch.js +25 -0
  652. package/dist/utils/fs-watch.js.map +1 -0
  653. package/dist/utils/git.d.ts +26 -0
  654. package/dist/utils/git.d.ts.map +1 -0
  655. package/dist/utils/git.js +195 -0
  656. package/dist/utils/git.js.map +1 -0
  657. package/dist/utils/html.d.ts +7 -0
  658. package/dist/utils/html.d.ts.map +1 -0
  659. package/dist/utils/html.js +40 -0
  660. package/dist/utils/html.js.map +1 -0
  661. package/dist/utils/image-convert.d.ts +9 -0
  662. package/dist/utils/image-convert.d.ts.map +1 -0
  663. package/dist/utils/image-convert.js +39 -0
  664. package/dist/utils/image-convert.js.map +1 -0
  665. package/dist/utils/image-resize-core.d.ts +30 -0
  666. package/dist/utils/image-resize-core.d.ts.map +1 -0
  667. package/dist/utils/image-resize-core.js +124 -0
  668. package/dist/utils/image-resize-core.js.map +1 -0
  669. package/dist/utils/image-resize-worker.d.ts +2 -0
  670. package/dist/utils/image-resize-worker.d.ts.map +1 -0
  671. package/dist/utils/image-resize-worker.js +31 -0
  672. package/dist/utils/image-resize-worker.js.map +1 -0
  673. package/dist/utils/image-resize.d.ts +16 -0
  674. package/dist/utils/image-resize.d.ts.map +1 -0
  675. package/dist/utils/image-resize.js +97 -0
  676. package/dist/utils/image-resize.js.map +1 -0
  677. package/dist/utils/json.d.ts +3 -0
  678. package/dist/utils/json.d.ts.map +1 -0
  679. package/dist/utils/json.js +7 -0
  680. package/dist/utils/json.js.map +1 -0
  681. package/dist/utils/mime.d.ts +3 -0
  682. package/dist/utils/mime.d.ts.map +1 -0
  683. package/dist/utils/mime.js +69 -0
  684. package/dist/utils/mime.js.map +1 -0
  685. package/dist/utils/open-browser.d.ts +9 -0
  686. package/dist/utils/open-browser.d.ts.map +1 -0
  687. package/dist/utils/open-browser.js +22 -0
  688. package/dist/utils/open-browser.js.map +1 -0
  689. package/dist/utils/paths.d.ts +31 -0
  690. package/dist/utils/paths.d.ts.map +1 -0
  691. package/dist/utils/paths.js +92 -0
  692. package/dist/utils/paths.js.map +1 -0
  693. package/dist/utils/photon.d.ts +21 -0
  694. package/dist/utils/photon.d.ts.map +1 -0
  695. package/dist/utils/photon.js +121 -0
  696. package/dist/utils/photon.js.map +1 -0
  697. package/dist/utils/pi-user-agent.d.ts +2 -0
  698. package/dist/utils/pi-user-agent.d.ts.map +1 -0
  699. package/dist/utils/pi-user-agent.js +5 -0
  700. package/dist/utils/pi-user-agent.js.map +1 -0
  701. package/dist/utils/shell.d.ts +31 -0
  702. package/dist/utils/shell.d.ts.map +1 -0
  703. package/dist/utils/shell.js +202 -0
  704. package/dist/utils/shell.js.map +1 -0
  705. package/dist/utils/sleep.d.ts +5 -0
  706. package/dist/utils/sleep.d.ts.map +1 -0
  707. package/dist/utils/sleep.js +17 -0
  708. package/dist/utils/sleep.js.map +1 -0
  709. package/dist/utils/syntax-highlight.d.ts +12 -0
  710. package/dist/utils/syntax-highlight.d.ts.map +1 -0
  711. package/dist/utils/syntax-highlight.js +118 -0
  712. package/dist/utils/syntax-highlight.js.map +1 -0
  713. package/dist/utils/tools-manager.d.ts +3 -0
  714. package/dist/utils/tools-manager.d.ts.map +1 -0
  715. package/dist/utils/tools-manager.js +328 -0
  716. package/dist/utils/tools-manager.js.map +1 -0
  717. package/dist/utils/version-check.d.ts +15 -0
  718. package/dist/utils/version-check.d.ts.map +1 -0
  719. package/dist/utils/version-check.js +52 -0
  720. package/dist/utils/version-check.js.map +1 -0
  721. package/dist/utils/windows-self-update.d.ts +3 -0
  722. package/dist/utils/windows-self-update.d.ts.map +1 -0
  723. package/dist/utils/windows-self-update.js +77 -0
  724. package/dist/utils/windows-self-update.js.map +1 -0
  725. package/docs/compaction.md +396 -0
  726. package/docs/containerization.md +111 -0
  727. package/docs/custom-provider.md +737 -0
  728. package/docs/development.md +71 -0
  729. package/docs/docs.json +156 -0
  730. package/docs/extensions.md +2681 -0
  731. package/docs/images/doom-extension.png +0 -0
  732. package/docs/images/exy.png +0 -0
  733. package/docs/images/interactive-mode.png +0 -0
  734. package/docs/images/tree-view.png +0 -0
  735. package/docs/index.md +82 -0
  736. package/docs/json.md +82 -0
  737. package/docs/keybindings.md +197 -0
  738. package/docs/models.md +495 -0
  739. package/docs/packages.md +227 -0
  740. package/docs/prompt-templates.md +95 -0
  741. package/docs/providers.md +274 -0
  742. package/docs/quickstart.md +165 -0
  743. package/docs/rpc.md +1412 -0
  744. package/docs/sdk.md +1143 -0
  745. package/docs/security.md +59 -0
  746. package/docs/session-format.md +412 -0
  747. package/docs/sessions.md +145 -0
  748. package/docs/settings.md +308 -0
  749. package/docs/shell-aliases.md +13 -0
  750. package/docs/skills.md +231 -0
  751. package/docs/terminal-setup.md +142 -0
  752. package/docs/termux.md +127 -0
  753. package/docs/themes.md +295 -0
  754. package/docs/tmux.md +63 -0
  755. package/docs/tui.md +927 -0
  756. package/docs/usage.md +308 -0
  757. package/docs/windows.md +17 -0
  758. package/examples/README.md +25 -0
  759. package/examples/extensions/README.md +211 -0
  760. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  761. package/examples/extensions/bash-spawn-hook.ts +30 -0
  762. package/examples/extensions/bookmark.ts +50 -0
  763. package/examples/extensions/border-status-editor.ts +150 -0
  764. package/examples/extensions/built-in-tool-renderer.ts +249 -0
  765. package/examples/extensions/claude-rules.ts +86 -0
  766. package/examples/extensions/commands.ts +72 -0
  767. package/examples/extensions/confirm-destructive.ts +59 -0
  768. package/examples/extensions/custom-compaction.ts +127 -0
  769. package/examples/extensions/custom-footer.ts +64 -0
  770. package/examples/extensions/custom-header.ts +73 -0
  771. package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  772. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  773. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  774. package/examples/extensions/custom-provider-gitlab-duo/index.ts +404 -0
  775. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  776. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  777. package/examples/extensions/dirty-repo-guard.ts +56 -0
  778. package/examples/extensions/doom-overlay/README.md +46 -0
  779. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  780. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  781. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  782. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  783. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  784. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  785. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  786. package/examples/extensions/doom-overlay/index.ts +74 -0
  787. package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  788. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  789. package/examples/extensions/dynamic-resources/dynamic.json +79 -0
  790. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  791. package/examples/extensions/dynamic-resources/index.ts +15 -0
  792. package/examples/extensions/dynamic-tools.ts +74 -0
  793. package/examples/extensions/event-bus.ts +43 -0
  794. package/examples/extensions/file-trigger.ts +41 -0
  795. package/examples/extensions/git-checkpoint.ts +53 -0
  796. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  797. package/examples/extensions/github-issue-autocomplete.ts +185 -0
  798. package/examples/extensions/gondolin/index.ts +531 -0
  799. package/examples/extensions/gondolin/package-lock.json +185 -0
  800. package/examples/extensions/gondolin/package.json +19 -0
  801. package/examples/extensions/handoff.ts +191 -0
  802. package/examples/extensions/hello.ts +26 -0
  803. package/examples/extensions/hidden-thinking-label.ts +53 -0
  804. package/examples/extensions/inline-bash.ts +94 -0
  805. package/examples/extensions/input-transform-streaming.ts +39 -0
  806. package/examples/extensions/input-transform.ts +43 -0
  807. package/examples/extensions/interactive-shell.ts +196 -0
  808. package/examples/extensions/mac-system-theme.ts +47 -0
  809. package/examples/extensions/message-renderer.ts +59 -0
  810. package/examples/extensions/minimal-mode.ts +426 -0
  811. package/examples/extensions/modal-editor.ts +85 -0
  812. package/examples/extensions/model-status.ts +31 -0
  813. package/examples/extensions/notify.ts +55 -0
  814. package/examples/extensions/overlay-qa-tests.ts +1450 -0
  815. package/examples/extensions/overlay-test.ts +153 -0
  816. package/examples/extensions/permission-gate.ts +34 -0
  817. package/examples/extensions/pirate.ts +47 -0
  818. package/examples/extensions/plan-mode/README.md +66 -0
  819. package/examples/extensions/plan-mode/index.ts +390 -0
  820. package/examples/extensions/plan-mode/utils.ts +168 -0
  821. package/examples/extensions/preset.ts +436 -0
  822. package/examples/extensions/project-trust.ts +64 -0
  823. package/examples/extensions/prompt-customizer.ts +97 -0
  824. package/examples/extensions/protected-paths.ts +30 -0
  825. package/examples/extensions/provider-payload.ts +18 -0
  826. package/examples/extensions/qna.ts +122 -0
  827. package/examples/extensions/question.ts +285 -0
  828. package/examples/extensions/questionnaire.ts +448 -0
  829. package/examples/extensions/rainbow-editor.ts +88 -0
  830. package/examples/extensions/reload-runtime.ts +37 -0
  831. package/examples/extensions/rpc-demo.ts +118 -0
  832. package/examples/extensions/sandbox/index.ts +321 -0
  833. package/examples/extensions/sandbox/package-lock.json +92 -0
  834. package/examples/extensions/sandbox/package.json +19 -0
  835. package/examples/extensions/send-user-message.ts +97 -0
  836. package/examples/extensions/session-name.ts +27 -0
  837. package/examples/extensions/shutdown-command.ts +63 -0
  838. package/examples/extensions/snake.ts +343 -0
  839. package/examples/extensions/space-invaders.ts +560 -0
  840. package/examples/extensions/ssh.ts +220 -0
  841. package/examples/extensions/status-line.ts +32 -0
  842. package/examples/extensions/structured-output.ts +65 -0
  843. package/examples/extensions/subagent/README.md +175 -0
  844. package/examples/extensions/subagent/agents/planner.md +37 -0
  845. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  846. package/examples/extensions/subagent/agents/scout.md +50 -0
  847. package/examples/extensions/subagent/agents/worker.md +24 -0
  848. package/examples/extensions/subagent/agents.ts +126 -0
  849. package/examples/extensions/subagent/index.ts +1015 -0
  850. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  851. package/examples/extensions/subagent/prompts/implement.md +10 -0
  852. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  853. package/examples/extensions/summarize.ts +206 -0
  854. package/examples/extensions/system-prompt-header.ts +17 -0
  855. package/examples/extensions/tic-tac-toe.ts +1008 -0
  856. package/examples/extensions/timed-confirm.ts +70 -0
  857. package/examples/extensions/titlebar-spinner.ts +58 -0
  858. package/examples/extensions/todo.ts +297 -0
  859. package/examples/extensions/tool-override.ts +144 -0
  860. package/examples/extensions/tools.ts +146 -0
  861. package/examples/extensions/trigger-compact.ts +50 -0
  862. package/examples/extensions/truncated-tool.ts +195 -0
  863. package/examples/extensions/widget-placement.ts +9 -0
  864. package/examples/extensions/with-deps/index.ts +32 -0
  865. package/examples/extensions/with-deps/package-lock.json +31 -0
  866. package/examples/extensions/with-deps/package.json +22 -0
  867. package/examples/extensions/working-indicator.ts +123 -0
  868. package/examples/extensions/working-message-test.ts +25 -0
  869. package/examples/rpc-extension-ui.ts +632 -0
  870. package/examples/sdk/01-minimal.ts +26 -0
  871. package/examples/sdk/02-custom-model.ts +53 -0
  872. package/examples/sdk/03-custom-prompt.ts +75 -0
  873. package/examples/sdk/04-skills.ts +55 -0
  874. package/examples/sdk/05-tools.ts +48 -0
  875. package/examples/sdk/06-extensions.ts +99 -0
  876. package/examples/sdk/07-context-files.ts +47 -0
  877. package/examples/sdk/08-prompt-templates.ts +51 -0
  878. package/examples/sdk/09-api-keys-and-oauth.ts +52 -0
  879. package/examples/sdk/10-settings.ts +53 -0
  880. package/examples/sdk/11-sessions.ts +52 -0
  881. package/examples/sdk/12-full-control.ts +77 -0
  882. package/examples/sdk/13-session-runtime.ts +67 -0
  883. package/examples/sdk/README.md +144 -0
  884. package/package.json +65 -0
@@ -0,0 +1,1416 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { TUI, visibleWidth } from "@earendil-works/pi-tui";
4
+ import { CURSOR_MARKER, renderFixedEditorCluster } from "../fixed-editor/cluster.ts";
5
+ import {
6
+ buildFixedClusterPaint,
7
+ emergencyTerminalModeReset,
8
+ endSynchronizedOutput,
9
+ beginSynchronizedOutput,
10
+ moveCursor,
11
+ resetScrollRegion,
12
+ setScrollRegion,
13
+ TerminalSplitCompositor,
14
+ } from "../fixed-editor/terminal-split.ts";
15
+
16
+ class FakeTerminal {
17
+ columns = 40;
18
+ private rowCount = 12;
19
+ writes: string[] = [];
20
+
21
+ get rows(): number {
22
+ return this.rowCount;
23
+ }
24
+
25
+ setRows(rows: number): void {
26
+ this.rowCount = rows;
27
+ }
28
+
29
+ write(data: string): void {
30
+ this.writes.push(data);
31
+ }
32
+
33
+ hideCursor(): void {}
34
+
35
+ showCursor(): void {}
36
+ }
37
+
38
+ test("fixed cluster keeps the editor visible before optional rows", () => {
39
+ const rendered = renderFixedEditorCluster({
40
+ width: 80,
41
+ terminalRows: 6,
42
+ statusLines: ["status"],
43
+ topLines: ["top"],
44
+ editorLines: ["edit-a", `edit-b ${CURSOR_MARKER}`, "edit-c"],
45
+ secondaryLines: ["secondary"],
46
+ transcriptLines: ["old-1", "old-2"],
47
+ lastPromptLines: ["last"],
48
+ });
49
+
50
+ assert.deepEqual(rendered.lines, ["top", "edit-a", "edit-b ", "edit-c", "secondary"]);
51
+ assert.deepEqual(rendered.cursor, { row: 2, col: 7 });
52
+ });
53
+
54
+ test("fixed cluster caps oversized editor around the cursor", () => {
55
+ const rendered = renderFixedEditorCluster({
56
+ width: 80,
57
+ terminalRows: 4,
58
+ statusLines: ["status"],
59
+ editorLines: ["edit-a", "edit-b", `edit-c ${CURSOR_MARKER}`, "edit-d", "edit-e"],
60
+ transcriptLines: ["old"],
61
+ });
62
+
63
+ assert.deepEqual(rendered.lines, ["edit-a", "edit-b", "edit-c "]);
64
+ assert.deepEqual(rendered.cursor, { row: 2, col: 7 });
65
+ });
66
+
67
+ test("fixed cluster caps selector-style editor replacements around the selected row", () => {
68
+ const rendered = renderFixedEditorCluster({
69
+ width: 80,
70
+ terminalRows: 4,
71
+ editorLines: [
72
+ "title",
73
+ " option-a",
74
+ " option-b",
75
+ "\x1b[38;5;39m→ \x1b[0m\x1b[38;5;39moption-c\x1b[0m",
76
+ " option-d",
77
+ "hint",
78
+ ],
79
+ });
80
+
81
+ assert.deepEqual(rendered.lines, [" option-b", "\x1b[38;5;39m→ \x1b[0m\x1b[38;5;39moption-c\x1b[0m", " option-d"]);
82
+ });
83
+
84
+ test("fixed cluster keeps tail status lines when compact", () => {
85
+ const rendered = renderFixedEditorCluster({
86
+ width: 80,
87
+ terminalRows: 3,
88
+ statusLines: ["above-widget", "powerline-status", "⠏ Shaolin Switchblade Sync..."],
89
+ editorLines: ["edit"],
90
+ });
91
+
92
+ assert.deepEqual(rendered.lines, ["⠏ Shaolin Switchblade Sync...", "edit"]);
93
+ });
94
+
95
+ test("terminal split can render a hidden status container in the fixed cluster", () => {
96
+ const terminal = new FakeTerminal();
97
+ const status = {
98
+ text: "⠏ Shaolin Switchblade Sync...",
99
+ render() {
100
+ return ["", this.text];
101
+ },
102
+ };
103
+ const editor = {
104
+ render() {
105
+ return ["editor"];
106
+ },
107
+ };
108
+ const tui = {
109
+ terminal,
110
+ render() {
111
+ return ["chat"];
112
+ },
113
+ doRender() {
114
+ this.terminal.write("body");
115
+ },
116
+ };
117
+
118
+ const compositor = new TerminalSplitCompositor({
119
+ tui,
120
+ terminal,
121
+ getShowHardwareCursor: () => false,
122
+ renderCluster: (width) => ({
123
+ lines: [
124
+ ...compositor.renderHidden(status, width).filter((line) => visibleWidth(line) > 0),
125
+ ...compositor.renderHidden(editor, width),
126
+ ],
127
+ cursor: null,
128
+ }),
129
+ });
130
+
131
+ compositor.hideRenderable(status);
132
+ compositor.hideRenderable(editor);
133
+ compositor.install();
134
+
135
+ assert.deepEqual(status.render(), []);
136
+ tui.doRender();
137
+ assert.ok(terminal.writes.at(-1)?.includes("⠏ Shaolin Switchblade Sync..."));
138
+
139
+ status.text = "⠙ Shaolin Switchblade Sync...";
140
+ compositor.requestRepaint();
141
+ assert.ok(terminal.writes.at(-1)?.includes("⠙ Shaolin Switchblade Sync..."));
142
+
143
+ compositor.dispose();
144
+ assert.deepEqual(status.render(), ["", "⠙ Shaolin Switchblade Sync..."]);
145
+ });
146
+
147
+ test("terminal split escape helpers generate DEC scroll region controls", () => {
148
+ assert.equal(beginSynchronizedOutput(), "\x1b[?2026h");
149
+ assert.equal(endSynchronizedOutput(), "\x1b[?2026l");
150
+ assert.equal(setScrollRegion(1, 18), "\x1b[1;18r");
151
+ assert.equal(resetScrollRegion(), "\x1b[r");
152
+ assert.equal(moveCursor(20, 3), "\x1b[20;3H");
153
+ });
154
+
155
+ test("fixed cluster paint clears bottom rows and positions hardware cursor", () => {
156
+ const paint = buildFixedClusterPaint(
157
+ { lines: ["top", "edit"], cursor: { row: 1, col: 2 } },
158
+ 10,
159
+ 20,
160
+ true,
161
+ );
162
+
163
+ assert.match(paint, /^\x1b\[r/);
164
+ assert.ok(paint.includes("\x1b[9;1H\x1b[2Ktop"));
165
+ assert.ok(paint.includes("\x1b[10;1H\x1b[2Kedit"));
166
+ assert.ok(paint.endsWith("\x1b[10;3H\x1b[?25h"));
167
+ });
168
+
169
+ test("terminal split reserves rows, hides root renderables, repaints, and cleans up", () => {
170
+ const terminal = new FakeTerminal();
171
+ const hidden = {
172
+ render(width: number) {
173
+ return [`hidden:${width}`];
174
+ },
175
+ };
176
+ const tui = {
177
+ terminal,
178
+ hardwareCursorRow: 2,
179
+ cursorRow: 2,
180
+ previousViewportTop: 0,
181
+ rendered: 0,
182
+ doRender() {
183
+ this.rendered += 1;
184
+ this.terminal.write("body");
185
+ },
186
+ };
187
+
188
+ const compositor = new TerminalSplitCompositor({
189
+ tui,
190
+ terminal,
191
+ getShowHardwareCursor: () => false,
192
+ renderCluster: (width) => ({
193
+ lines: [`cluster:${width}`, ...compositor.renderHidden(hidden, width)],
194
+ cursor: null,
195
+ }),
196
+ });
197
+
198
+ compositor.hideRenderable(hidden);
199
+ compositor.install();
200
+
201
+ assert.deepEqual(hidden.render(40), []);
202
+ assert.equal(terminal.rows, 10);
203
+
204
+ tui.doRender();
205
+
206
+ assert.equal(tui.rendered, 1);
207
+ assert.equal(terminal.writes.length, 3);
208
+ assert.ok(terminal.writes[0]?.includes("\x1b[?1049h"));
209
+ assert.ok(terminal.writes[0]?.includes("\x1b[?1007l"));
210
+ assert.ok(terminal.writes[0]?.includes("\x1b[?1002h"));
211
+ assert.ok(terminal.writes[0]?.includes("\x1b[?1006h"));
212
+ assert.ok(terminal.writes[1]?.includes("\x1b[1;10r\x1b[3;1Hbody"));
213
+ assert.ok(terminal.writes[1]?.includes("cluster:40"));
214
+ assert.ok(terminal.writes[1]?.includes("hidden:40"));
215
+ assert.ok(terminal.writes[2]?.includes("cluster:40"));
216
+
217
+ compositor.dispose();
218
+
219
+ assert.deepEqual(hidden.render(8), ["hidden:8"]);
220
+ assert.equal(terminal.rows, 12);
221
+ assert.ok(terminal.writes.at(-1)?.includes("\x1b[r"));
222
+ assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1006l"));
223
+ assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1002l"));
224
+ assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1000l"));
225
+ assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1007h"));
226
+ assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1049l"));
227
+ assert.ok(!terminal.writes.at(-1)?.includes("\x1b[<u"));
228
+ assert.ok(!terminal.writes.at(-1)?.includes("\x1b[>4;0m"));
229
+ });
230
+
231
+ test("terminal split re-enables Kitty keyboard protocol in alternate screen", () => {
232
+ const terminal = new FakeTerminal();
233
+ Object.defineProperty(terminal, "kittyProtocolActive", { value: true });
234
+ const compositor = new TerminalSplitCompositor({
235
+ tui: { terminal },
236
+ terminal,
237
+ renderCluster: () => ({ lines: ["cluster"], cursor: null }),
238
+ });
239
+
240
+ compositor.install();
241
+
242
+ const setup = terminal.writes[0] ?? "";
243
+ assert.ok(setup.includes("\x1b[?1049h"));
244
+ assert.ok(setup.includes("\x1b[>7u"));
245
+ assert.ok(setup.indexOf("\x1b[?1049h") < setup.indexOf("\x1b[>7u"));
246
+
247
+ compositor.dispose();
248
+
249
+ const cleanup = terminal.writes.at(-1) ?? "";
250
+ assert.ok(cleanup.includes("\x1b[<u"));
251
+ assert.ok(cleanup.indexOf("\x1b[<u") < cleanup.indexOf("\x1b[?1049l"));
252
+ assert.ok(!cleanup.includes("\x1b[<999u"));
253
+ });
254
+
255
+ test("terminal split re-enables modifyOtherKeys in alternate screen", () => {
256
+ const terminal = new FakeTerminal();
257
+ Reflect.set(terminal, "_modifyOtherKeysActive", true);
258
+ const compositor = new TerminalSplitCompositor({
259
+ tui: { terminal },
260
+ terminal,
261
+ renderCluster: () => ({ lines: ["cluster"], cursor: null }),
262
+ });
263
+
264
+ compositor.install();
265
+
266
+ const setup = terminal.writes[0] ?? "";
267
+ assert.ok(setup.includes("\x1b[?1049h"));
268
+ assert.ok(setup.includes("\x1b[>4;2m"));
269
+ assert.ok(setup.indexOf("\x1b[?1049h") < setup.indexOf("\x1b[>4;2m"));
270
+
271
+ compositor.dispose();
272
+
273
+ const cleanup = terminal.writes.at(-1) ?? "";
274
+ assert.ok(cleanup.includes("\x1b[>4;0m"));
275
+ assert.ok(cleanup.indexOf("\x1b[>4;0m") < cleanup.indexOf("\x1b[?1049l"));
276
+ assert.ok(!cleanup.includes("\x1b[<999u"));
277
+ });
278
+
279
+ test("terminal split restores main screen mode when Kitty activates after install", () => {
280
+ const terminal = new FakeTerminal();
281
+ const compositor = new TerminalSplitCompositor({
282
+ tui: { terminal },
283
+ terminal,
284
+ renderCluster: () => ({ lines: ["cluster"], cursor: null }),
285
+ });
286
+
287
+ compositor.install();
288
+ terminal.kittyProtocolActive = true;
289
+ compositor.dispose();
290
+
291
+ const cleanup = terminal.writes.at(-1) ?? "";
292
+ assert.ok(cleanup.includes("\x1b[<u"));
293
+ assert.ok(cleanup.includes("\x1b[>7u"));
294
+ assert.ok(cleanup.indexOf("\x1b[<u") < cleanup.indexOf("\x1b[?1049l"));
295
+ assert.ok(cleanup.indexOf("\x1b[?1049l") < cleanup.indexOf("\x1b[>7u"));
296
+ });
297
+
298
+ test("terminal split restores main screen mode when modifyOtherKeys activates after install", () => {
299
+ const terminal = new FakeTerminal();
300
+ const compositor = new TerminalSplitCompositor({
301
+ tui: { terminal },
302
+ terminal,
303
+ renderCluster: () => ({ lines: ["cluster"], cursor: null }),
304
+ });
305
+
306
+ compositor.install();
307
+ Reflect.set(terminal, "_modifyOtherKeysActive", true);
308
+ compositor.dispose();
309
+
310
+ const cleanup = terminal.writes.at(-1) ?? "";
311
+ assert.ok(cleanup.includes("\x1b[>4;0m"));
312
+ assert.ok(cleanup.includes("\x1b[>4;2m"));
313
+ assert.ok(cleanup.indexOf("\x1b[>4;0m") < cleanup.indexOf("\x1b[?1049l"));
314
+ assert.ok(cleanup.indexOf("\x1b[?1049l") < cleanup.indexOf("\x1b[>4;2m"));
315
+ });
316
+
317
+ test("terminal split shutdown cleanup resets extended keyboard modes", () => {
318
+ const terminal = new FakeTerminal();
319
+ const compositor = new TerminalSplitCompositor({
320
+ tui: { terminal },
321
+ terminal,
322
+ renderCluster: () => ({ lines: ["cluster"], cursor: null }),
323
+ });
324
+
325
+ compositor.install();
326
+ compositor.dispose({ resetExtendedKeyboardModes: true });
327
+
328
+ const cleanup = terminal.writes.at(-1) ?? "";
329
+ assert.ok(cleanup.includes("\x1b[<999u"));
330
+ assert.ok(cleanup.includes("\x1b[>4;0m"));
331
+ assert.ok(cleanup.indexOf("\x1b[?1049l") < cleanup.indexOf("\x1b[<999u"));
332
+ });
333
+
334
+ test("terminal row reservation does not recurse when hidden editor render reads terminal rows", () => {
335
+ const terminal = new FakeTerminal();
336
+ const tui = { terminal };
337
+ const hidden = {
338
+ render() {
339
+ return [`rows:${terminal.rows}`];
340
+ },
341
+ };
342
+
343
+ const compositor = new TerminalSplitCompositor({
344
+ tui,
345
+ terminal,
346
+ renderCluster: (width) => ({
347
+ lines: compositor.renderHidden(hidden, width),
348
+ cursor: null,
349
+ }),
350
+ });
351
+
352
+ compositor.hideRenderable(hidden);
353
+ compositor.install();
354
+
355
+ assert.equal(terminal.rows, 11);
356
+ compositor.requestRepaint();
357
+ assert.ok(terminal.writes.at(-1)?.includes("rows:12"));
358
+
359
+ compositor.dispose();
360
+ });
361
+
362
+ test("terminal split anchors diff writes to the visible viewport row", () => {
363
+ const terminal = new FakeTerminal();
364
+ const tui = {
365
+ terminal,
366
+ hardwareCursorRow: 100,
367
+ cursorRow: 100,
368
+ previousViewportTop: 95,
369
+ };
370
+
371
+ const compositor = new TerminalSplitCompositor({
372
+ tui,
373
+ terminal,
374
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
375
+ });
376
+
377
+ compositor.install();
378
+ terminal.write("diff");
379
+
380
+ assert.ok(terminal.writes[1]?.includes("\x1b[1;10r\x1b[6;1Hdiff"));
381
+
382
+ compositor.dispose();
383
+ });
384
+
385
+ test("terminal split does not repaint the fixed cluster over visible overlays", () => {
386
+ const terminal = new FakeTerminal();
387
+ const tui = {
388
+ terminal,
389
+ overlayStack: [{}],
390
+ rendered: 0,
391
+ doRender() {
392
+ this.rendered += 1;
393
+ this.terminal.write("overlay-frame");
394
+ },
395
+ };
396
+
397
+ const compositor = new TerminalSplitCompositor({
398
+ tui,
399
+ terminal,
400
+ renderCluster: () => ({ lines: ["cluster"], cursor: null }),
401
+ });
402
+
403
+ compositor.install();
404
+ tui.doRender();
405
+ compositor.requestRepaint();
406
+
407
+ assert.deepEqual(terminal.writes, [
408
+ "\x1b[?2026h\x1b[?1049h\x1b[?1007l\x1b[?1002h\x1b[?1006h\x1b[?2026l",
409
+ "overlay-frame",
410
+ ]);
411
+
412
+ compositor.dispose();
413
+ });
414
+
415
+ test("terminal split strips OSC markers from root lines while overlays are visible", () => {
416
+ const terminal = new FakeTerminal();
417
+ terminal.columns = 20;
418
+ const tui = {
419
+ terminal,
420
+ overlayStack: [{}],
421
+ render() {
422
+ return ["\x1b]133;B\x07" + "x".repeat(20) + "\x1b]133;C\x07"];
423
+ },
424
+ };
425
+
426
+ const compositor = new TerminalSplitCompositor({
427
+ tui,
428
+ terminal,
429
+ renderCluster: () => ({ lines: ["cluster"], cursor: null }),
430
+ });
431
+
432
+ compositor.install();
433
+ const rendered = tui.render(20);
434
+
435
+ assert.equal(rendered.length, 1);
436
+ assert.equal(visibleWidth(rendered[0] ?? ""), 20);
437
+ assert.doesNotMatch(rendered[0] ?? "", /\]133/);
438
+
439
+ compositor.dispose();
440
+ });
441
+
442
+ test("terminal split keeps tabbed overlay composition within terminal width", () => {
443
+ const terminal = new FakeTerminal();
444
+ terminal.columns = 250;
445
+ terminal.setRows(40);
446
+ const tui = new TUI(terminal, false);
447
+ const overlay = "\x1b[38;2;119;125;136m[grep]: render.ts-706- \treturn [...lines.slice(0, visibleLines), truncLine(theme.fg(\"dim\", hint), width)];\x1b[39m";
448
+
449
+ const before = tui.compositeLineAt("Validation before " + " ".repeat(232), overlay, 20, 210, 250);
450
+ assert.ok(visibleWidth(before) > 250);
451
+
452
+ const compositor = new TerminalSplitCompositor({
453
+ tui,
454
+ terminal,
455
+ renderCluster: () => ({ lines: ["cluster"], cursor: null }),
456
+ });
457
+
458
+ compositor.install();
459
+ const after = tui.compositeLineAt("Validation before " + " ".repeat(232), overlay, 20, 210, 250);
460
+
461
+ assert.ok(visibleWidth(after) <= 250);
462
+ assert.doesNotMatch(after, /\t/);
463
+
464
+ compositor.dispose();
465
+ });
466
+
467
+ test("terminal split renders chat through an app-owned scroll viewport", () => {
468
+ const terminal = new FakeTerminal();
469
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
470
+ const renderRequests: Array<boolean | undefined> = [];
471
+ let rootLines = Array.from({ length: 15 }, (_, index) => `line-${index}`);
472
+ const tui = {
473
+ terminal,
474
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
475
+ inputListener = listener;
476
+ return () => {
477
+ inputListener = null;
478
+ };
479
+ },
480
+ requestRender(force?: boolean) {
481
+ renderRequests.push(force);
482
+ },
483
+ render() {
484
+ return rootLines;
485
+ },
486
+ };
487
+
488
+ const compositor = new TerminalSplitCompositor({
489
+ tui,
490
+ terminal,
491
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
492
+ });
493
+
494
+ compositor.install();
495
+
496
+ assert.equal(terminal.rows, 10);
497
+ assert.deepEqual(tui.render(40), [
498
+ "line-5", "line-6", "line-7", "line-8", "line-9",
499
+ "line-10", "line-11", "line-12", "line-13", "line-14",
500
+ ]);
501
+
502
+ assert.deepEqual(inputListener?.("\x1b[<2;1;1M"), { consume: true });
503
+ assert.deepEqual(inputListener?.("\x1bOA"), undefined);
504
+ assert.deepEqual(inputListener?.("\x1bOB"), undefined);
505
+ assert.deepEqual(inputListener?.("\x1b[A"), undefined);
506
+ assert.deepEqual(renderRequests, []);
507
+ assert.deepEqual(inputListener?.("\x1b[<64;1;1M"), { consume: true });
508
+ assert.deepEqual(renderRequests, [undefined]);
509
+ assert.deepEqual(tui.render(40), [
510
+ "line-2", "line-3", "line-4", "line-5", "line-6",
511
+ "line-7", "line-8", "line-9", "line-10", "line-11",
512
+ ]);
513
+
514
+ rootLines = [...rootLines, "line-15"];
515
+ assert.deepEqual(tui.render(40), [
516
+ "line-2", "line-3", "line-4", "line-5", "line-6",
517
+ "line-7", "line-8", "line-9", "line-10", "line-11",
518
+ ]);
519
+
520
+ assert.deepEqual(inputListener?.("\x1b[<65;1;1M"), { consume: true });
521
+ assert.deepEqual(renderRequests, [undefined, undefined]);
522
+ assert.deepEqual(tui.render(40), [
523
+ "line-5", "line-6", "line-7", "line-8", "line-9",
524
+ "line-10", "line-11", "line-12", "line-13", "line-14",
525
+ ]);
526
+
527
+ assert.deepEqual(inputListener?.("\x1b[5~"), { consume: true });
528
+ assert.deepEqual(renderRequests, [undefined, undefined, undefined]);
529
+ assert.deepEqual(tui.render(40), [
530
+ "line-0", "line-1", "line-2", "line-3", "line-4",
531
+ "line-5", "line-6", "line-7", "line-8", "line-9",
532
+ ]);
533
+
534
+ compositor.dispose();
535
+ assert.equal(inputListener, null);
536
+ });
537
+
538
+ test("terminal split refreshes scroll bounds after fixed status rows appear", () => {
539
+ const terminal = new FakeTerminal();
540
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
541
+ const renderRequests: Array<boolean | undefined> = [];
542
+ const rootLines = Array.from({ length: 11 }, (_, index) => `line-${index}`);
543
+ let statusVisible = false;
544
+ const tui = {
545
+ terminal,
546
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
547
+ inputListener = listener;
548
+ return () => {
549
+ inputListener = null;
550
+ };
551
+ },
552
+ requestRender(force?: boolean) {
553
+ renderRequests.push(force);
554
+ },
555
+ render() {
556
+ return rootLines;
557
+ },
558
+ };
559
+
560
+ const compositor = new TerminalSplitCompositor({
561
+ tui,
562
+ terminal,
563
+ renderCluster: () => ({
564
+ lines: statusVisible ? ["⠏ fixed status", "editor"] : ["editor"],
565
+ cursor: null,
566
+ }),
567
+ });
568
+
569
+ compositor.install();
570
+
571
+ assert.deepEqual(tui.render(40), rootLines);
572
+
573
+ statusVisible = true;
574
+ compositor.requestRepaint();
575
+
576
+ assert.deepEqual(inputListener?.("\x1b[<64;1;1M"), { consume: true });
577
+ assert.deepEqual(renderRequests, [undefined]);
578
+ assert.match(terminal.writes.at(-1) ?? "", /line-0/);
579
+ assert.match(terminal.writes.at(-1) ?? "", /⠏ fixed status/);
580
+ assert.deepEqual(tui.render(40), [
581
+ "line-0", "line-1", "line-2", "line-3", "line-4",
582
+ "line-5", "line-6", "line-7", "line-8", "line-9",
583
+ ]);
584
+
585
+ compositor.dispose();
586
+ });
587
+
588
+ test("terminal split handles modified SGR wheel packets", () => {
589
+ const terminal = new FakeTerminal();
590
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
591
+ const renderRequests: Array<boolean | undefined> = [];
592
+ const tui = {
593
+ terminal,
594
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
595
+ inputListener = listener;
596
+ return () => {
597
+ inputListener = null;
598
+ };
599
+ },
600
+ requestRender(force?: boolean) {
601
+ renderRequests.push(force);
602
+ },
603
+ render() {
604
+ return Array.from({ length: 15 }, (_, index) => `line-${index}`);
605
+ },
606
+ };
607
+
608
+ const compositor = new TerminalSplitCompositor({
609
+ tui,
610
+ terminal,
611
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
612
+ });
613
+
614
+ compositor.install();
615
+ tui.render(40);
616
+
617
+ assert.deepEqual(inputListener?.("\x1b[<68;1;1M"), { consume: true });
618
+ assert.deepEqual(renderRequests, [undefined]);
619
+ assert.deepEqual(tui.render(40), [
620
+ "line-2", "line-3", "line-4", "line-5", "line-6",
621
+ "line-7", "line-8", "line-9", "line-10", "line-11",
622
+ ]);
623
+
624
+ assert.deepEqual(inputListener?.("\x1b[<68;1;1M\x1b[<68;1;1M"), { consume: true });
625
+ assert.deepEqual(renderRequests, [undefined, undefined]);
626
+ assert.deepEqual(tui.render(40), [
627
+ "line-0", "line-1", "line-2", "line-3", "line-4",
628
+ "line-5", "line-6", "line-7", "line-8", "line-9",
629
+ ]);
630
+
631
+ assert.deepEqual(inputListener?.("\x1b[<69;1;1M"), { consume: true });
632
+ assert.deepEqual(renderRequests, [undefined, undefined, undefined]);
633
+
634
+ compositor.dispose();
635
+ });
636
+
637
+ test("terminal split pauses mouse reporting on right click for the terminal context menu", () => {
638
+ const terminal = new FakeTerminal();
639
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
640
+ const renderRequests: Array<boolean | undefined> = [];
641
+ const copied: string[] = [];
642
+ const tui = {
643
+ terminal,
644
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
645
+ inputListener = listener;
646
+ return () => {
647
+ inputListener = null;
648
+ };
649
+ },
650
+ requestRender(force?: boolean) {
651
+ renderRequests.push(force);
652
+ },
653
+ render() {
654
+ return Array.from({ length: 20 }, (_, index) => `line-${index}`);
655
+ },
656
+ };
657
+
658
+ const compositor = new TerminalSplitCompositor({
659
+ tui,
660
+ terminal,
661
+ onCopySelection: (text) => copied.push(text),
662
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
663
+ });
664
+
665
+ compositor.install();
666
+ tui.render(40);
667
+
668
+ assert.deepEqual(inputListener?.("\x1b[<0;5;5M"), { consume: true });
669
+ assert.deepEqual(inputListener?.("\x1b[<0;5;5m"), { consume: true });
670
+ assert.deepEqual(inputListener?.("\x1b[<2;5;5M"), { consume: true });
671
+ assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1006l\x1b[?1002l\x1b[?1000l"));
672
+ assert.deepEqual(inputListener?.("\x1b[<0;5;5M"), { consume: true });
673
+ assert.deepEqual(inputListener?.("\x1b[<0;5;5m"), { consume: true });
674
+ assert.deepEqual(copied, []);
675
+ assert.deepEqual(renderRequests, [undefined, undefined, undefined, undefined]);
676
+
677
+ compositor.dispose();
678
+ });
679
+
680
+ test("terminal split selects visible chat text and copies it on drag release", () => {
681
+ const terminal = new FakeTerminal();
682
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
683
+ const renderRequests: Array<boolean | undefined> = [];
684
+ const copied: string[] = [];
685
+ const rootLines = [
686
+ "old-0", "old-1", "old-2", "old-3", "old-4",
687
+ "alpha one", "bravo two", "charlie three", "delta four", "echo five",
688
+ "foxtrot six", "golf seven", "hotel eight", "india nine", "juliet ten",
689
+ ];
690
+ const tui = {
691
+ terminal,
692
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
693
+ inputListener = listener;
694
+ return () => {
695
+ inputListener = null;
696
+ };
697
+ },
698
+ requestRender(force?: boolean) {
699
+ renderRequests.push(force);
700
+ },
701
+ render() {
702
+ return rootLines;
703
+ },
704
+ };
705
+
706
+ const compositor = new TerminalSplitCompositor({
707
+ tui,
708
+ terminal,
709
+ onCopySelection: (text) => copied.push(text),
710
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
711
+ });
712
+
713
+ compositor.install();
714
+ assert.deepEqual(tui.render(40), [
715
+ "alpha one", "bravo two", "charlie three", "delta four", "echo five",
716
+ "foxtrot six", "golf seven", "hotel eight", "india nine", "juliet ten",
717
+ ]);
718
+
719
+ assert.deepEqual(inputListener?.("\x1b[<0;2;2M"), { consume: true });
720
+ assert.deepEqual(inputListener?.("\x1b[<32;7;4M"), { consume: true });
721
+ assert.deepEqual(tui.render(40).slice(1, 4), [
722
+ "b\x1b[7mravo two\x1b[27m",
723
+ "\x1b[7mcharlie three\x1b[27m",
724
+ "\x1b[7mdelta \x1b[27mfour",
725
+ ]);
726
+ assert.deepEqual(inputListener?.("\x1b[<0;7;4m"), { consume: true });
727
+
728
+ assert.deepEqual(copied, ["ravo two\ncharlie three\ndelta"]);
729
+ assert.ok(!terminal.writes.at(-1)?.includes("\x1b[?1006l\x1b[?1002l\x1b[?1000l"));
730
+ assert.deepEqual(renderRequests, [undefined, undefined, undefined]);
731
+
732
+ compositor.dispose();
733
+ });
734
+
735
+ test("terminal split restores app-owned selection after context menu copy", (t) => {
736
+ t.mock.timers.enable({ apis: ["setTimeout"] });
737
+
738
+ const terminal = new FakeTerminal();
739
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
740
+ let clipboard = "";
741
+ const rootLines = [
742
+ "old-0", "old-1", "old-2", "old-3", "old-4",
743
+ "alpha one", "bravo two", "charlie three", "delta four", "echo five",
744
+ "foxtrot six", "golf seven", "hotel eight", "india nine", "juliet ten",
745
+ ];
746
+ const tui = {
747
+ terminal,
748
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
749
+ inputListener = listener;
750
+ return () => {
751
+ inputListener = null;
752
+ };
753
+ },
754
+ requestRender() {},
755
+ render(_width?: number) {
756
+ return rootLines;
757
+ },
758
+ };
759
+
760
+ const compositor = new TerminalSplitCompositor({
761
+ tui,
762
+ terminal,
763
+ onCopySelection: (text) => {
764
+ clipboard = text;
765
+ },
766
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
767
+ });
768
+
769
+ compositor.install();
770
+ tui.render(40);
771
+
772
+ assert.deepEqual(inputListener?.("\x1b[<0;2;2M"), { consume: true });
773
+ assert.deepEqual(inputListener?.("\x1b[<32;7;4M"), { consume: true });
774
+ assert.deepEqual(inputListener?.("\x1b[<0;7;4m"), { consume: true });
775
+ assert.equal(clipboard, "ravo two\ncharlie three\ndelta");
776
+
777
+ clipboard = "clicked-word";
778
+ assert.deepEqual(inputListener?.("\x1b[<2;4;3M"), { consume: true });
779
+ assert.equal(clipboard, "ravo two\ncharlie three\ndelta");
780
+ assert.ok(tui.render(40)[1]?.includes("b\x1b[7mravo two\x1b[27m"));
781
+ assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1006l\x1b[?1002l\x1b[?1000l"));
782
+
783
+ clipboard = "terminal-clicked-word-copy";
784
+ t.mock.timers.tick(1200);
785
+ assert.equal(clipboard, "ravo two\ncharlie three\ndelta");
786
+ assert.ok(terminal.writes.at(-1)?.includes("\x1b[?1002h\x1b[?1006h"));
787
+
788
+ clipboard = "late-terminal-clicked-word-copy";
789
+ t.mock.timers.tick(100);
790
+ assert.equal(clipboard, "ravo two\ncharlie three\ndelta");
791
+
792
+ clipboard = "other-copy";
793
+ assert.deepEqual(inputListener?.("\x1b[<2;5;5M"), { consume: true });
794
+ assert.equal(clipboard, "other-copy");
795
+ t.mock.timers.tick(1200);
796
+ assert.equal(clipboard, "other-copy");
797
+ assert.ok(!tui.render(40)[1]?.includes("\x1b[7mravo two\x1b[27m"));
798
+
799
+ compositor.dispose();
800
+ });
801
+
802
+ test("terminal split selection does not expose OSC control sequences as text", () => {
803
+ const terminal = new FakeTerminal();
804
+ terminal.columns = 20;
805
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
806
+ const rootLines = [
807
+ "old-0", "old-1", "old-2", "old-3", "old-4",
808
+ "alpha", "bravo", "charlie", "delta", "echo",
809
+ "foxtrot", "golf", "hotel", "india", "\x1b]133;B\x07\x1b]133;C\x07juliet",
810
+ ];
811
+ const tui = {
812
+ terminal,
813
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
814
+ inputListener = listener;
815
+ return () => {
816
+ inputListener = null;
817
+ };
818
+ },
819
+ requestRender() {},
820
+ render() {
821
+ return rootLines;
822
+ },
823
+ };
824
+
825
+ const compositor = new TerminalSplitCompositor({
826
+ tui,
827
+ terminal,
828
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
829
+ });
830
+
831
+ compositor.install();
832
+ tui.render(20);
833
+
834
+ assert.deepEqual(inputListener?.("\x1b[<0;1;10M"), { consume: true });
835
+ assert.deepEqual(inputListener?.("\x1b[<32;6;10M"), { consume: true });
836
+ const selectedLine = tui.render(20).at(-1) ?? "";
837
+
838
+ assert.ok(visibleWidth(selectedLine) <= 20);
839
+ assert.ok(!selectedLine.includes("]133"));
840
+ assert.ok(selectedLine.includes("\x1b[7mjulie\x1b[27mt"));
841
+
842
+ compositor.dispose();
843
+ });
844
+
845
+ test("terminal split selection highlighting does not duplicate wide glyphs", () => {
846
+ for (const glyph of ["🪃", "👨‍👩‍👧‍👦"]) {
847
+ const terminal = new FakeTerminal();
848
+ terminal.columns = 30;
849
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
850
+ const prefix = "Done shows ";
851
+ const line = `${prefix}${glyph} auto${" ".repeat(30 - visibleWidth(`${prefix}${glyph} auto`))}`;
852
+ const tui = {
853
+ terminal,
854
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
855
+ inputListener = listener;
856
+ return () => {
857
+ inputListener = null;
858
+ };
859
+ },
860
+ requestRender() {},
861
+ render() {
862
+ return ["old-0", "old-1", "old-2", "old-3", "old-4", "old-5", "old-6", "old-7", "old-8", line];
863
+ },
864
+ };
865
+
866
+ const compositor = new TerminalSplitCompositor({
867
+ tui,
868
+ terminal,
869
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
870
+ });
871
+
872
+ compositor.install();
873
+ tui.render(30);
874
+
875
+ const glyphCol = visibleWidth(prefix);
876
+ assert.deepEqual(inputListener?.(`\x1b[<0;${glyphCol + 1};10M`), { consume: true });
877
+ assert.deepEqual(inputListener?.(`\x1b[<32;${glyphCol + 2};10M`), { consume: true });
878
+ const selectedLine = tui.render(30).at(-1) ?? "";
879
+
880
+ assert.ok(visibleWidth(selectedLine) <= 30);
881
+ assert.equal(selectedLine.includes(glyph), true);
882
+ assert.ok(selectedLine.includes(`\x1b[7m${glyph}\x1b[27m`));
883
+
884
+ compositor.dispose();
885
+ }
886
+ });
887
+
888
+ test("terminal split copies chat and fixed cluster selections", () => {
889
+ const terminal = new FakeTerminal();
890
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
891
+ const copied: string[] = [];
892
+ const rootLines = [
893
+ "old-0", "old-1", "old-2", "old-3", "old-4",
894
+ "alpha one", "bravo two", "charlie three", "delta four", "echo five",
895
+ "foxtrot six", "golf seven", "hotel eight", "india nine", "juliet ten",
896
+ ];
897
+ const tui = {
898
+ terminal,
899
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
900
+ inputListener = listener;
901
+ return () => {
902
+ inputListener = null;
903
+ };
904
+ },
905
+ requestRender() {},
906
+ render() {
907
+ return rootLines;
908
+ },
909
+ };
910
+
911
+ const compositor = new TerminalSplitCompositor({
912
+ tui,
913
+ terminal,
914
+ onCopySelection: (text) => copied.push(text),
915
+ renderCluster: () => ({ lines: ["cluster-a", " > hello world"], cursor: null }),
916
+ });
917
+
918
+ compositor.install();
919
+ tui.render(40);
920
+
921
+ assert.deepEqual(inputListener?.("\x1b[<0;1;9M"), { consume: true });
922
+ assert.deepEqual(inputListener?.("\x1b[<0;5;11m"), { consume: true });
923
+ assert.deepEqual(copied, ["india nine\njuli"]);
924
+
925
+ assert.deepEqual(inputListener?.("\x1b[<0;5;12M"), { consume: true });
926
+ assert.deepEqual(inputListener?.("\x1b[<32;10;12M"), { consume: true });
927
+ compositor.requestRepaint();
928
+ assert.ok(terminal.writes.at(-1)?.includes(" > \x1b[7mhello\x1b[27m world"));
929
+ assert.deepEqual(inputListener?.("\x1b[<0;10;12m"), { consume: true });
930
+ assert.deepEqual(copied, ["india nine\njuli", "hello"]);
931
+
932
+ assert.deepEqual(inputListener?.("\x1b[<0;8;12M"), { consume: true });
933
+ assert.deepEqual(inputListener?.("\x1b[<0;8;12m"), { consume: true });
934
+ assert.deepEqual(copied, ["india nine\njuli", "hello"]);
935
+
936
+ assert.deepEqual(inputListener?.("\x1b[<0;4;3M"), { consume: true });
937
+ assert.deepEqual(inputListener?.("\x1b[<0;4;3m"), { consume: true });
938
+ assert.deepEqual(inputListener?.("\x1b[<0;5;3M"), { consume: true });
939
+ assert.ok(tui.render(40)[2]?.includes("\x1b[7mcharlie three\x1b[27m"));
940
+ assert.deepEqual(inputListener?.("\x1b[<0;5;3m"), { consume: true });
941
+ assert.deepEqual(copied, ["india nine\njuli", "hello", "charlie three"]);
942
+
943
+ assert.deepEqual(inputListener?.("\x1b[<0;8;12M"), { consume: true });
944
+ assert.deepEqual(inputListener?.("\x1b[<0;8;12m"), { consume: true });
945
+ assert.deepEqual(inputListener?.("\x1b[<0;9;12M"), { consume: true });
946
+ compositor.requestRepaint();
947
+ assert.ok(terminal.writes.at(-1)?.includes("\x1b[7m > hello world\x1b[27m"));
948
+ assert.deepEqual(inputListener?.("\x1b[<0;9;12m"), { consume: true });
949
+ assert.deepEqual(copied, ["india nine\njuli", "hello", "charlie three", " > hello world"]);
950
+
951
+ compositor.dispose();
952
+ });
953
+
954
+ test("terminal split selection scrolls when dragged to viewport edges", () => {
955
+ const terminal = new FakeTerminal();
956
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
957
+ const copied: string[] = [];
958
+ const tui = {
959
+ terminal,
960
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
961
+ inputListener = listener;
962
+ return () => {
963
+ inputListener = null;
964
+ };
965
+ },
966
+ requestRender() {},
967
+ render() {
968
+ return Array.from({ length: 30 }, (_, index) => `line-${index}`);
969
+ },
970
+ };
971
+
972
+ const compositor = new TerminalSplitCompositor({
973
+ tui,
974
+ terminal,
975
+ onCopySelection: (text) => copied.push(text),
976
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
977
+ });
978
+
979
+ compositor.install();
980
+ tui.render();
981
+ inputListener?.("\x1b[5~");
982
+ assert.equal(tui.render()[0], "line-10");
983
+
984
+ assert.deepEqual(inputListener?.("\x1b[<0;1;9M"), { consume: true });
985
+ assert.deepEqual(inputListener?.("\x1b[<32;5;12M"), { consume: true });
986
+ assert.equal(tui.render()[0], "line-11");
987
+ assert.ok(tui.render()[9]?.includes("\x1b[7mline\x1b[27m-20"));
988
+ assert.deepEqual(inputListener?.("\x1b[<0;5;12m"), { consume: true });
989
+ assert.deepEqual(copied, ["line-18\nline-19\nline"]);
990
+
991
+ compositor.jumpToRootBottom();
992
+ assert.equal(tui.render()[0], "line-20");
993
+ assert.deepEqual(inputListener?.("\x1b[<0;1;2M"), { consume: true });
994
+ assert.deepEqual(inputListener?.("\x1b[<32;5;1M"), { consume: true });
995
+ assert.equal(tui.render()[0]?.replace(/\x1b\[[0-9;]*m/g, ""), "line-19");
996
+ assert.ok(tui.render()[0]?.includes("line\x1b[7m-19\x1b[27m"));
997
+ assert.deepEqual(inputListener?.("\x1b[<0;5;1m"), { consume: true });
998
+ assert.deepEqual(copied, ["line-18\nline-19\nline", "-19\nline-20"]);
999
+
1000
+ compositor.dispose();
1001
+ });
1002
+
1003
+ test("terminal split copies edge-scrolled selections without waiting for render", () => {
1004
+ const terminal = new FakeTerminal();
1005
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1006
+ const copied: string[] = [];
1007
+ const tui = {
1008
+ terminal,
1009
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1010
+ inputListener = listener;
1011
+ return () => {
1012
+ inputListener = null;
1013
+ };
1014
+ },
1015
+ requestRender() {},
1016
+ render() {
1017
+ return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1018
+ },
1019
+ };
1020
+
1021
+ const compositor = new TerminalSplitCompositor({
1022
+ tui,
1023
+ terminal,
1024
+ onCopySelection: (text) => copied.push(text),
1025
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1026
+ });
1027
+
1028
+ compositor.install();
1029
+ tui.render();
1030
+ inputListener?.("\x1b[5~");
1031
+ tui.render();
1032
+
1033
+ inputListener?.("\x1b[<0;1;9M");
1034
+ inputListener?.("\x1b[<32;5;12M");
1035
+ inputListener?.("\x1b[<0;5;12m");
1036
+ assert.deepEqual(copied, ["line-18\nline-19\nline"]);
1037
+
1038
+ inputListener?.("\x1b[<0;1;9M");
1039
+ for (let i = 0; i < 9; i++) {
1040
+ inputListener?.("\x1b[<32;5;12M");
1041
+ }
1042
+ inputListener?.("\x1b[<0;5;12m");
1043
+ assert.deepEqual(copied.at(-1), [
1044
+ "line-19", "line-20", "line-21", "line-22", "line-23",
1045
+ "line-24", "line-25", "line-26", "line-27", "line-28", "line",
1046
+ ].join("\n"));
1047
+
1048
+ compositor.dispose();
1049
+ });
1050
+
1051
+ test("terminal split maps post-edge-scroll drags against the updated viewport", () => {
1052
+ const terminal = new FakeTerminal();
1053
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1054
+ const copied: string[] = [];
1055
+ const tui = {
1056
+ terminal,
1057
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1058
+ inputListener = listener;
1059
+ return () => {
1060
+ inputListener = null;
1061
+ };
1062
+ },
1063
+ requestRender() {},
1064
+ render() {
1065
+ return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1066
+ },
1067
+ };
1068
+
1069
+ const compositor = new TerminalSplitCompositor({
1070
+ tui,
1071
+ terminal,
1072
+ onCopySelection: (text) => copied.push(text),
1073
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1074
+ });
1075
+
1076
+ compositor.install();
1077
+ tui.render();
1078
+ inputListener?.("\x1b[5~");
1079
+ tui.render();
1080
+
1081
+ inputListener?.("\x1b[<0;1;9M");
1082
+ inputListener?.("\x1b[<32;1;12M");
1083
+ inputListener?.("\x1b[<32;1;3M");
1084
+ inputListener?.("\x1b[<0;5;4m");
1085
+ assert.deepEqual(copied, ["-14\nline-15\nline-16\nline-17"]);
1086
+
1087
+ compositor.dispose();
1088
+ });
1089
+
1090
+ test("terminal split keyboard scroll supports Pi page aliases and preserves app shortcuts", () => {
1091
+ const terminal = new FakeTerminal();
1092
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1093
+ const tui = {
1094
+ terminal,
1095
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1096
+ inputListener = listener;
1097
+ return () => {
1098
+ inputListener = null;
1099
+ };
1100
+ },
1101
+ requestRender() {},
1102
+ render() {
1103
+ return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1104
+ },
1105
+ };
1106
+
1107
+ const compositor = new TerminalSplitCompositor({
1108
+ tui,
1109
+ terminal,
1110
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1111
+ });
1112
+
1113
+ compositor.install();
1114
+ tui.render();
1115
+
1116
+ assert.deepEqual(inputListener?.("\x1b[5~"), { consume: true });
1117
+ assert.deepEqual(inputListener?.("\x1b[5;9~"), { consume: true });
1118
+ assert.deepEqual(inputListener?.("\x1b[57421;9u"), { consume: true });
1119
+ assert.deepEqual(inputListener?.("\x1b[1;9A"), { consume: true });
1120
+ assert.deepEqual(inputListener?.("\x1b[57419;9u"), { consume: true });
1121
+ assert.deepEqual(inputListener?.("\x1b[1;9H"), { consume: true });
1122
+ assert.deepEqual(inputListener?.("\x1b[57423;9u"), { consume: true });
1123
+ assert.deepEqual(inputListener?.("\x1b[7;9~"), { consume: true });
1124
+ assert.deepEqual(inputListener?.("\x1b[1;6A"), { consume: true });
1125
+ assert.deepEqual(inputListener?.("\x1b[57419;6u"), { consume: true });
1126
+ assert.deepEqual(inputListener?.("\x1b[6;9~"), { consume: true });
1127
+ assert.deepEqual(inputListener?.("\x1b[57422;9u"), { consume: true });
1128
+ assert.deepEqual(inputListener?.("\x1b[1;9B"), { consume: true });
1129
+ assert.deepEqual(inputListener?.("\x1b[57420;9u"), { consume: true });
1130
+ assert.deepEqual(inputListener?.("\x1b[1;9F"), { consume: true });
1131
+ assert.deepEqual(inputListener?.("\x1b[57424;9u"), { consume: true });
1132
+ assert.deepEqual(inputListener?.("\x1b[8;9~"), { consume: true });
1133
+ assert.deepEqual(inputListener?.("\x1b[1;6B"), { consume: true });
1134
+ assert.deepEqual(inputListener?.("\x1b[57420;6u"), { consume: true });
1135
+ assert.equal(inputListener?.("\x1b[1;10A"), undefined);
1136
+ assert.equal(inputListener?.("\x1b[57419;10u"), undefined);
1137
+ assert.equal(inputListener?.("\x1b[1;10B"), undefined);
1138
+ assert.equal(inputListener?.("\x1b[57420;10u"), undefined);
1139
+ assert.equal(inputListener?.("\x1b[1;10:3A"), undefined);
1140
+ assert.equal(inputListener?.("\x1b[57419;10:3u"), undefined);
1141
+ assert.equal(inputListener?.("\x1bp"), undefined);
1142
+ assert.equal(inputListener?.("\x1bn"), undefined);
1143
+
1144
+ compositor.dispose();
1145
+ });
1146
+
1147
+ test("terminal split keyboard scroll accepts configured shortcuts", () => {
1148
+ const terminal = new FakeTerminal();
1149
+ const renderRequests: Array<boolean | undefined> = [];
1150
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1151
+ const tui = {
1152
+ terminal,
1153
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1154
+ inputListener = listener;
1155
+ return () => {
1156
+ inputListener = null;
1157
+ };
1158
+ },
1159
+ requestRender(force?: boolean) {
1160
+ renderRequests.push(force);
1161
+ },
1162
+ render() {
1163
+ return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1164
+ },
1165
+ };
1166
+
1167
+ const compositor = new TerminalSplitCompositor({
1168
+ tui,
1169
+ terminal,
1170
+ keyboardScrollShortcuts: { up: "ctrl+shift+u", down: "ctrl+shift+d" },
1171
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1172
+ });
1173
+
1174
+ compositor.install();
1175
+ tui.render();
1176
+
1177
+ assert.equal(inputListener?.("\x1b[1;9A"), undefined);
1178
+ assert.deepEqual(inputListener?.("\x1b[117;6u"), { consume: true });
1179
+ assert.deepEqual(renderRequests, [undefined]);
1180
+ assert.deepEqual(inputListener?.("\x1b[100;6u"), { consume: true });
1181
+ assert.deepEqual(renderRequests, [undefined, undefined]);
1182
+
1183
+ compositor.dispose();
1184
+ });
1185
+
1186
+ test("terminal split jumps to previous root target lines", () => {
1187
+ const terminal = new FakeTerminal();
1188
+ const renderRequests: Array<boolean | undefined> = [];
1189
+ const tui = {
1190
+ terminal,
1191
+ requestRender(force?: boolean) {
1192
+ renderRequests.push(force);
1193
+ },
1194
+ render() {
1195
+ return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1196
+ },
1197
+ };
1198
+
1199
+ const compositor = new TerminalSplitCompositor({
1200
+ tui,
1201
+ terminal,
1202
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1203
+ });
1204
+
1205
+ compositor.install();
1206
+ assert.deepEqual(tui.render(40), [
1207
+ "line-20", "line-21", "line-22", "line-23", "line-24",
1208
+ "line-25", "line-26", "line-27", "line-28", "line-29",
1209
+ ]);
1210
+
1211
+ assert.equal(compositor.jumpToPreviousRootTarget([6, 14, 24]), true);
1212
+ assert.deepEqual(renderRequests, [undefined]);
1213
+ assert.deepEqual(tui.render(40), [
1214
+ "line-14", "line-15", "line-16", "line-17", "line-18",
1215
+ "line-19", "line-20", "line-21", "line-22", "line-23",
1216
+ ]);
1217
+
1218
+ assert.equal(compositor.jumpToPreviousRootTarget([6, 14, 24]), true);
1219
+ assert.deepEqual(renderRequests, [undefined, undefined]);
1220
+ assert.deepEqual(tui.render(40), [
1221
+ "line-6", "line-7", "line-8", "line-9", "line-10",
1222
+ "line-11", "line-12", "line-13", "line-14", "line-15",
1223
+ ]);
1224
+
1225
+ assert.equal(compositor.jumpToPreviousRootTarget([6, 14, 24]), false);
1226
+
1227
+ assert.equal(compositor.jumpToNextRootTarget([6, 14, 24]), true);
1228
+ assert.deepEqual(tui.render(40), [
1229
+ "line-14", "line-15", "line-16", "line-17", "line-18",
1230
+ "line-19", "line-20", "line-21", "line-22", "line-23",
1231
+ ]);
1232
+
1233
+ assert.equal(compositor.jumpToNextRootTarget([6, 14, 24]), true);
1234
+ assert.deepEqual(tui.render(40), [
1235
+ "line-20", "line-21", "line-22", "line-23", "line-24",
1236
+ "line-25", "line-26", "line-27", "line-28", "line-29",
1237
+ ]);
1238
+
1239
+ assert.equal(compositor.jumpToNextRootTarget([6, 14, 24]), false);
1240
+
1241
+ assert.equal(compositor.jumpToPreviousRootTarget([6, 14, 24]), true);
1242
+ assert.deepEqual(tui.render(40), [
1243
+ "line-14", "line-15", "line-16", "line-17", "line-18",
1244
+ "line-19", "line-20", "line-21", "line-22", "line-23",
1245
+ ]);
1246
+ assert.equal(compositor.jumpToRootBottom(), true);
1247
+ assert.deepEqual(tui.render(40), [
1248
+ "line-20", "line-21", "line-22", "line-23", "line-24",
1249
+ "line-25", "line-26", "line-27", "line-28", "line-29",
1250
+ ]);
1251
+ assert.equal(compositor.jumpToRootBottom(), false);
1252
+ compositor.dispose();
1253
+ });
1254
+
1255
+ test("terminal split previous root target only moves to older targets", () => {
1256
+ const terminal = new FakeTerminal();
1257
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1258
+ const tui = {
1259
+ terminal,
1260
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1261
+ inputListener = listener;
1262
+ return () => {
1263
+ inputListener = null;
1264
+ };
1265
+ },
1266
+ requestRender() {},
1267
+ render() {
1268
+ return Array.from({ length: 30 }, (_, index) => `line-${index}`);
1269
+ },
1270
+ };
1271
+
1272
+ const compositor = new TerminalSplitCompositor({
1273
+ tui,
1274
+ terminal,
1275
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1276
+ });
1277
+
1278
+ compositor.install();
1279
+ assert.deepEqual(tui.render(), [
1280
+ "line-20", "line-21", "line-22", "line-23", "line-24",
1281
+ "line-25", "line-26", "line-27", "line-28", "line-29",
1282
+ ]);
1283
+ assert.deepEqual(inputListener?.("\x1b[5~"), { consume: true });
1284
+ assert.deepEqual(tui.render(), [
1285
+ "line-10", "line-11", "line-12", "line-13", "line-14",
1286
+ "line-15", "line-16", "line-17", "line-18", "line-19",
1287
+ ]);
1288
+
1289
+ assert.equal(compositor.jumpToPreviousRootTarget([6, 14, 24]), true);
1290
+ assert.deepEqual(tui.render(), [
1291
+ "line-6", "line-7", "line-8", "line-9", "line-10",
1292
+ "line-11", "line-12", "line-13", "line-14", "line-15",
1293
+ ]);
1294
+
1295
+ compositor.dispose();
1296
+ });
1297
+
1298
+ test("terminal split can disable mouse reporting for normal selection", () => {
1299
+ const terminal = new FakeTerminal();
1300
+ let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
1301
+ const renderRequests: Array<boolean | undefined> = [];
1302
+ const tui = {
1303
+ terminal,
1304
+ addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
1305
+ inputListener = listener;
1306
+ return () => {
1307
+ inputListener = null;
1308
+ };
1309
+ },
1310
+ requestRender(force?: boolean) {
1311
+ renderRequests.push(force);
1312
+ },
1313
+ render() {
1314
+ return Array.from({ length: 15 }, (_, index) => `line-${index}`);
1315
+ },
1316
+ };
1317
+
1318
+ const compositor = new TerminalSplitCompositor({
1319
+ tui,
1320
+ terminal,
1321
+ mouseScroll: false,
1322
+ renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
1323
+ });
1324
+
1325
+ compositor.install();
1326
+ tui.render(40);
1327
+
1328
+ assert.ok(!terminal.writes[0]?.includes("\x1b[?1002h"));
1329
+ assert.ok(!terminal.writes[0]?.includes("\x1b[?1006h"));
1330
+ assert.deepEqual(inputListener?.("\x1b[<64;1;1M"), undefined);
1331
+ assert.deepEqual(inputListener?.("\x1b[A"), undefined);
1332
+ assert.deepEqual(inputListener?.("\x1b[5~"), { consume: true });
1333
+ assert.deepEqual(renderRequests, [undefined]);
1334
+
1335
+ compositor.dispose();
1336
+ assert.ok(!terminal.writes.at(-1)?.includes("\x1b[?1006l"));
1337
+ assert.ok(!terminal.writes.at(-1)?.includes("\x1b[?1002l"));
1338
+ });
1339
+
1340
+ test("terminal split reuses the fixed cluster during one render pass", () => {
1341
+ const terminal = new FakeTerminal();
1342
+ let renderClusterCount = 0;
1343
+ const tui = {
1344
+ terminal,
1345
+ hardwareCursorRow: 0,
1346
+ cursorRow: 0,
1347
+ previousViewportTop: 0,
1348
+ render() {
1349
+ return ["root-a", "root-b"];
1350
+ },
1351
+ doRender() {
1352
+ void this.terminal.rows;
1353
+ this.render(this.terminal.columns);
1354
+ this.terminal.write("body");
1355
+ },
1356
+ };
1357
+
1358
+ const compositor = new TerminalSplitCompositor({
1359
+ tui,
1360
+ terminal,
1361
+ renderCluster: () => {
1362
+ renderClusterCount += 1;
1363
+ return { lines: ["cluster-a", "cluster-b"], cursor: null };
1364
+ },
1365
+ });
1366
+
1367
+ compositor.install();
1368
+ tui.doRender();
1369
+
1370
+ assert.equal(renderClusterCount, 1);
1371
+
1372
+ compositor.dispose();
1373
+ });
1374
+
1375
+ test("emergency terminal reset exits alternate screen before clearing keyboard modes", () => {
1376
+ const cleanup = emergencyTerminalModeReset();
1377
+ assert.ok(cleanup.includes("\x1b[?1049l"));
1378
+ assert.ok(cleanup.includes("\x1b[<999u"));
1379
+ assert.ok(cleanup.includes("\x1b[>4;0m"));
1380
+ assert.ok(cleanup.indexOf("\x1b[?1049l") < cleanup.indexOf("\x1b[<999u"));
1381
+ });
1382
+
1383
+ test("terminal split emergency exit cleanup resets extended keyboard modes", () => {
1384
+ const terminal = new FakeTerminal();
1385
+ const compositor = new TerminalSplitCompositor({
1386
+ tui: { terminal },
1387
+ terminal,
1388
+ renderCluster: () => ({ lines: ["cluster"], cursor: null }),
1389
+ });
1390
+
1391
+ compositor.install();
1392
+ process.emit("exit", 0);
1393
+
1394
+ const cleanup = terminal.writes.at(-1) ?? "";
1395
+ assert.ok(cleanup.includes("\x1b[<999u"));
1396
+ assert.ok(cleanup.includes("\x1b[>4;0m"));
1397
+ assert.ok(cleanup.indexOf("\x1b[?1049l") < cleanup.indexOf("\x1b[<999u"));
1398
+
1399
+ compositor.dispose();
1400
+ });
1401
+
1402
+ test("terminal split unregisters emergency exit cleanup on dispose", () => {
1403
+ const terminal = new FakeTerminal();
1404
+ const before = process.listenerCount("exit");
1405
+ const compositor = new TerminalSplitCompositor({
1406
+ tui: { terminal },
1407
+ terminal,
1408
+ renderCluster: () => ({ lines: ["cluster"], cursor: null }),
1409
+ });
1410
+
1411
+ compositor.install();
1412
+ assert.equal(process.listenerCount("exit"), before + 1);
1413
+
1414
+ compositor.dispose();
1415
+ assert.equal(process.listenerCount("exit"), before);
1416
+ });