@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,2817 @@
1
+ import {
2
+ copyToClipboard,
3
+ type ExtensionAPI,
4
+ type ReadonlyFooterDataProvider,
5
+ type Theme,
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import type { AssistantMessage } from "@earendil-works/pi-ai";
8
+ import { isKeyRelease, matchesKey, type AutocompleteProvider, type SelectItem, SelectList, truncateToWidth, TUI_KEYBINDINGS, visibleWidth } from "@earendil-works/pi-tui";
9
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
10
+ import { join, dirname } from "node:path";
11
+ import { homedir } from "node:os";
12
+
13
+ import type { ColorScheme, SegmentContext, StatusLinePreset, StatusLineSegmentId } from "./types.ts";
14
+ import type { PowerlineConfig } from "./powerline-config.ts";
15
+ import { BashTranscriptStore } from "./bash-mode/transcript.ts";
16
+ import {
17
+ BashCompletionEngine,
18
+ BashAutocompleteProvider,
19
+ getOneOffBashCommandContext,
20
+ ModeAwareAutocompleteProvider,
21
+ OneOffBashAutocompleteProvider,
22
+ } from "./bash-mode/completion.ts";
23
+ import { BashModeEditor } from "./bash-mode/editor.ts";
24
+ import { ManagedShellSession } from "./bash-mode/shell-session.ts";
25
+ import { matchHistoryEntries, readGlobalShellHistory, readProjectHistory, appendProjectHistory } from "./bash-mode/history.ts";
26
+ import type { BashModeSettings } from "./bash-mode/types.ts";
27
+ import { getPreset, PRESETS } from "./presets.ts";
28
+ import { collectHiddenExtensionStatusKeys, getNotificationExtensionStatuses, mergeSegmentsWithCustomItems, nextPowerlineSettingWithOptions, nextPowerlineSettingWithPreset, parsePowerlineConfig } from "./powerline-config.ts";
29
+ import { getSeparator } from "./separators.ts";
30
+ import { renderSegment } from "./segments.ts";
31
+ import { getGitStatus, invalidateGitStatus, invalidateGitBranch } from "./git-status.ts";
32
+ import { ansi, getFgAnsiCode } from "./colors.ts";
33
+ import { WelcomeComponent, WelcomeHeader, discoverLoadedCounts, getRecentSessions } from "./welcome.ts";
34
+ import { createWelcomeDismissScheduler } from "./welcome-dismiss.ts";
35
+ import { createRenderScheduler } from "./render-scheduler.ts";
36
+ import { readCoreContextUsage } from "./context-usage.ts";
37
+ import { renderFixedEditorCluster } from "./fixed-editor/cluster.ts";
38
+ import { emergencyTerminalModeReset, TerminalSplitCompositor } from "./fixed-editor/terminal-split.ts";
39
+ import { getDefaultColors } from "./theme.ts";
40
+ import {
41
+ isSupportedSuperShortcut,
42
+ matchesConfiguredShortcut,
43
+ shortcutConflictKey,
44
+ shortcutUsesSuper,
45
+ } from "./shortcuts.ts";
46
+ import {
47
+ initVibeManager,
48
+ onVibeBeforeAgentStart,
49
+ onVibeAgentStart,
50
+ onVibeAgentEnd,
51
+ onVibeToolCall,
52
+ getVibeTheme,
53
+ setVibeTheme,
54
+ getVibeModel,
55
+ setVibeModel,
56
+ getVibeMode,
57
+ setVibeMode,
58
+ hasVibeFile,
59
+ getVibeFileCount,
60
+ generateVibesBatch,
61
+ } from "./working-vibes.ts";
62
+
63
+ // ═══════════════════════════════════════════════════════════════════════════
64
+ // Configuration
65
+ // ═══════════════════════════════════════════════════════════════════════════
66
+
67
+ let config: PowerlineConfig = {
68
+ preset: "default",
69
+ customItems: [],
70
+ mouseScroll: true,
71
+ fixedEditor: true,
72
+ fixedEditorPromptGlyph: "",
73
+ };
74
+
75
+ const CUSTOM_COMPACTION_STATUS_KEY = "compact-policy";
76
+ let customCompactionEnabled = false;
77
+
78
+ interface PowerlineShortcuts {
79
+ stashHistory: string;
80
+ copyEditor: string;
81
+ cutEditor: string;
82
+ jumpPreviousUserMessage: string;
83
+ jumpNextUserMessage: string;
84
+ jumpPreviousLlmMessage: string;
85
+ jumpNextLlmMessage: string;
86
+ jumpChatBottom: string;
87
+ scrollChatUp: string;
88
+ scrollChatDown: string;
89
+ editorStart: string;
90
+ editorEnd: string;
91
+ }
92
+
93
+ type PowerlineShortcutKey = keyof PowerlineShortcuts;
94
+ type ChatJumpShortcutKey = Extract<PowerlineShortcutKey,
95
+ | "jumpPreviousUserMessage"
96
+ | "jumpNextUserMessage"
97
+ | "jumpPreviousLlmMessage"
98
+ | "jumpNextLlmMessage"
99
+ | "jumpChatBottom"
100
+ >;
101
+ type ChatJumpRole = "user" | "assistant";
102
+ type ChatJumpDirection = "previous" | "next";
103
+ type ChatJumpShortcutAction =
104
+ | { kind: "message"; role: ChatJumpRole; direction: ChatJumpDirection }
105
+ | { kind: "bottom" };
106
+ type PowerlineShortcutAction =
107
+ | { kind: "stashHistory" }
108
+ | { kind: "copyEditor" }
109
+ | { kind: "cutEditor" }
110
+ | { kind: "bashMode" }
111
+ | { kind: "chat"; action: ChatJumpShortcutAction };
112
+
113
+ const STASH_HISTORY_LIMIT = 12;
114
+ const PROJECT_PROMPT_HISTORY_LIMIT = 50;
115
+ const STASH_PREVIEW_WIDTH = 72;
116
+ const DEFAULT_SHORTCUTS: PowerlineShortcuts = {
117
+ stashHistory: "ctrl+alt+h",
118
+ copyEditor: "ctrl+alt+c",
119
+ cutEditor: "ctrl+alt+x",
120
+ jumpPreviousUserMessage: "ctrl+shift+u",
121
+ jumpNextUserMessage: "ctrl+shift+i",
122
+ jumpPreviousLlmMessage: "ctrl+alt+,",
123
+ jumpNextLlmMessage: "ctrl+alt+.",
124
+ jumpChatBottom: "ctrl+shift+g",
125
+ scrollChatUp: "super+up",
126
+ scrollChatDown: "super+down",
127
+ editorStart: "super+shift+up",
128
+ editorEnd: "super+shift+down",
129
+ };
130
+ const DEFAULT_BASH_MODE_SETTINGS: BashModeSettings = {
131
+ toggleShortcut: "ctrl+shift+b",
132
+ transcriptMaxLines: 2000,
133
+ transcriptMaxBytes: 512 * 1024,
134
+ };
135
+ const CHAT_JUMP_SHORTCUTS: Array<{
136
+ shortcutKey: ChatJumpShortcutKey;
137
+ description: string;
138
+ action: ChatJumpShortcutAction;
139
+ }> = [
140
+ {
141
+ shortcutKey: "jumpPreviousUserMessage",
142
+ description: "Jump to previous user message",
143
+ action: { kind: "message", role: "user", direction: "previous" },
144
+ },
145
+ {
146
+ shortcutKey: "jumpNextUserMessage",
147
+ description: "Jump to next user message",
148
+ action: { kind: "message", role: "user", direction: "next" },
149
+ },
150
+ {
151
+ shortcutKey: "jumpPreviousLlmMessage",
152
+ description: "Jump to previous LLM message",
153
+ action: { kind: "message", role: "assistant", direction: "previous" },
154
+ },
155
+ {
156
+ shortcutKey: "jumpNextLlmMessage",
157
+ description: "Jump to next LLM message",
158
+ action: { kind: "message", role: "assistant", direction: "next" },
159
+ },
160
+ {
161
+ shortcutKey: "jumpChatBottom",
162
+ description: "Jump chat to bottom",
163
+ action: { kind: "bottom" },
164
+ },
165
+ ];
166
+ const SHORTCUT_KEYS: PowerlineShortcutKey[] = [
167
+ "stashHistory",
168
+ "copyEditor",
169
+ "cutEditor",
170
+ "jumpPreviousUserMessage",
171
+ "jumpNextUserMessage",
172
+ "jumpPreviousLlmMessage",
173
+ "jumpNextLlmMessage",
174
+ "jumpChatBottom",
175
+ "scrollChatUp",
176
+ "scrollChatDown",
177
+ "editorStart",
178
+ "editorEnd",
179
+ ];
180
+ const APP_RESERVED_SHORTCUTS = [
181
+ "escape",
182
+ "ctrl+c",
183
+ "ctrl+d",
184
+ "ctrl+z",
185
+ "shift+tab",
186
+ "ctrl+p",
187
+ "shift+ctrl+p",
188
+ "ctrl+l",
189
+ "ctrl+o",
190
+ "shift+ctrl+o",
191
+ "ctrl+t",
192
+ "ctrl+n",
193
+ "ctrl+g",
194
+ "alt+enter",
195
+ "alt+up",
196
+ "alt+down",
197
+ "ctrl+v",
198
+ "alt+v",
199
+ "shift+l",
200
+ "shift+t",
201
+ "ctrl+s",
202
+ "ctrl+r",
203
+ "ctrl+backspace",
204
+ "ctrl+a",
205
+ "ctrl+x",
206
+ "ctrl+u",
207
+ ] as const;
208
+ const EXTRA_RESERVED_SHORTCUTS = ["alt+s"] as const;
209
+ const SHORTCUT_MODIFIER_ORDER = ["ctrl", "alt", "super", "shift"] as const;
210
+ const SHORTCUT_MODIFIERS = new Set(SHORTCUT_MODIFIER_ORDER);
211
+ const SHORTCUT_NAMED_KEYS = new Set([
212
+ "escape", "esc", "enter", "return", "tab", "space", "backspace", "delete", "insert", "clear",
213
+ "home", "end", "pageup", "pagedown", "up", "down", "left", "right",
214
+ ]);
215
+ const SHORTCUT_SYMBOL_KEYS = new Set([
216
+ "`", "-", "=", "[", "]", "\\", ";", "'", ",", ".", "/",
217
+ "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "|", "~", "{", "}", ":", "<", ">", "?",
218
+ ]);
219
+ const PROMPT_HISTORY_LIMIT = 100;
220
+ const LAYOUT_CACHE_TTL_MS = 250;
221
+ const STREAMING_LAYOUT_CACHE_TTL_MS = 1000;
222
+ const STATUS_RENDER_DEBOUNCE_MS = 33;
223
+ const CONTEXT_STATUS_RENDER_MS = 250;
224
+ const EDITOR_STATUS_DEFER_MS = 150;
225
+ const PROMPT_HISTORY_TRACKED = Symbol.for("powerlinePromptHistoryTracked");
226
+ const PROMPT_HISTORY_STATE_KEY = Symbol.for("powerlinePromptHistoryState");
227
+
228
+ type PromptHistoryState = { savedPromptHistory: string[] };
229
+ type SessionAssistantUsage = AssistantMessage["usage"];
230
+
231
+ function getUsageTokenTotal(usage: SessionAssistantUsage): number {
232
+ const totalTokens = "totalTokens" in usage && typeof usage.totalTokens === "number" ? usage.totalTokens : 0;
233
+ return totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
234
+ }
235
+
236
+ function hasSessionAssistantUsage(value: unknown): value is SessionAssistantUsage {
237
+ if (!isRecord(value)) {
238
+ return false;
239
+ }
240
+
241
+ if (
242
+ typeof value.input !== "number" ||
243
+ typeof value.output !== "number" ||
244
+ typeof value.cacheRead !== "number" ||
245
+ typeof value.cacheWrite !== "number"
246
+ ) {
247
+ return false;
248
+ }
249
+
250
+ return isRecord(value.cost) && typeof value.cost.total === "number";
251
+ }
252
+
253
+ function isSessionAssistantMessage(value: unknown): value is AssistantMessage {
254
+ return isRecord(value)
255
+ && value.role === "assistant"
256
+ && hasSessionAssistantUsage(value.usage)
257
+ && (value.stopReason === undefined || typeof value.stopReason === "string");
258
+ }
259
+
260
+ function isPromptHistoryState(value: unknown): value is PromptHistoryState {
261
+ return isRecord(value)
262
+ && Array.isArray(value.savedPromptHistory)
263
+ && value.savedPromptHistory.every((entry) => typeof entry === "string");
264
+ }
265
+
266
+ function getPromptHistoryState(): PromptHistoryState {
267
+ const existing = Reflect.get(globalThis, PROMPT_HISTORY_STATE_KEY);
268
+ if (isPromptHistoryState(existing)) {
269
+ return existing;
270
+ }
271
+
272
+ const state: PromptHistoryState = { savedPromptHistory: [] };
273
+ Reflect.set(globalThis, PROMPT_HISTORY_STATE_KEY, state);
274
+ return state;
275
+ }
276
+
277
+ function readPromptHistory(editor: any): string[] {
278
+ const history = editor?.history;
279
+ if (!Array.isArray(history)) return [];
280
+
281
+ const normalized: string[] = [];
282
+ for (const entry of history) {
283
+ if (typeof entry !== "string") continue;
284
+ const trimmed = entry.trim();
285
+ if (!trimmed) continue;
286
+ if (normalized.length > 0 && normalized[normalized.length - 1] === trimmed) continue;
287
+ normalized.push(trimmed);
288
+ if (normalized.length >= PROMPT_HISTORY_LIMIT) break;
289
+ }
290
+
291
+ return normalized;
292
+ }
293
+
294
+ function snapshotPromptHistory(editor: any): void {
295
+ const history = readPromptHistory(editor);
296
+ if (history.length > 0) {
297
+ getPromptHistoryState().savedPromptHistory = [...history];
298
+ }
299
+ }
300
+
301
+ function restorePromptHistory(editor: any): void {
302
+ const { savedPromptHistory } = getPromptHistoryState();
303
+ if (!savedPromptHistory.length || typeof editor?.addToHistory !== "function") return;
304
+
305
+ for (let i = savedPromptHistory.length - 1; i >= 0; i--) {
306
+ editor.addToHistory(savedPromptHistory[i]);
307
+ }
308
+ }
309
+
310
+ function trackPromptHistory(editor: any): void {
311
+ if (!editor || typeof editor.addToHistory !== "function") return;
312
+ if (editor[PROMPT_HISTORY_TRACKED]) {
313
+ snapshotPromptHistory(editor);
314
+ return;
315
+ }
316
+
317
+ const originalAddToHistory = editor.addToHistory.bind(editor);
318
+ editor.addToHistory = (text: string) => {
319
+ originalAddToHistory(text);
320
+ snapshotPromptHistory(editor);
321
+ };
322
+ editor[PROMPT_HISTORY_TRACKED] = true;
323
+ snapshotPromptHistory(editor);
324
+ }
325
+
326
+ function getSettingsPath(): string {
327
+ const homeDir = process.env.HOME || process.env.USERPROFILE || homedir();
328
+ return join(homeDir, ".pi", "agent", "settings.json");
329
+ }
330
+
331
+ function getProjectSettingsPath(cwd: string): string {
332
+ return join(cwd, ".pi", "settings.json");
333
+ }
334
+
335
+ function getGlobalCompactionPolicyPath(): string {
336
+ const homeDir = process.env.HOME || process.env.USERPROFILE || homedir();
337
+ return join(homeDir, ".pi", "agent", "compaction-policy.json");
338
+ }
339
+
340
+ function getCustomCompactionExtensionPath(): string {
341
+ const homeDir = process.env.HOME || process.env.USERPROFILE || homedir();
342
+ return join(homeDir, ".pi", "agent", "extensions", "pi-custom-compaction");
343
+ }
344
+
345
+ function mergeSettings(base: Record<string, unknown>, override: Record<string, unknown>): Record<string, unknown> {
346
+ const merged: Record<string, unknown> = { ...base };
347
+
348
+ for (const [key, overrideValue] of Object.entries(override)) {
349
+ const baseValue = merged[key];
350
+ merged[key] = isRecord(baseValue) && isRecord(overrideValue)
351
+ ? mergeSettings(baseValue, overrideValue)
352
+ : overrideValue;
353
+ }
354
+
355
+ return merged;
356
+ }
357
+
358
+ function readSettingsFile(settingsPath: string): Record<string, unknown> {
359
+ try {
360
+ if (!existsSync(settingsPath)) {
361
+ return {};
362
+ }
363
+
364
+ const parsed = JSON.parse(readFileSync(settingsPath, "utf-8"));
365
+ if (!isRecord(parsed)) {
366
+ console.debug(`[powerline-footer] Ignoring non-object settings at ${settingsPath}`);
367
+ return {};
368
+ }
369
+
370
+ return parsed;
371
+ } catch (error) {
372
+ // Settings are user-edited input. Log and keep the extension running with defaults
373
+ // instead of crashing the UI during startup.
374
+ console.debug(`[powerline-footer] Failed to read settings from ${settingsPath}:`, error);
375
+ return {};
376
+ }
377
+ }
378
+
379
+ function readWritableSettingsFile(settingsPath: string): Record<string, unknown> | null {
380
+ if (!existsSync(settingsPath)) {
381
+ return {};
382
+ }
383
+
384
+ try {
385
+ const parsed = JSON.parse(readFileSync(settingsPath, "utf-8"));
386
+ if (!isRecord(parsed)) {
387
+ console.debug(`[powerline-footer] Refusing to write settings to non-object file at ${settingsPath}`);
388
+ return null;
389
+ }
390
+
391
+ return parsed;
392
+ } catch (error) {
393
+ // Do not overwrite malformed user settings with partial data. Surface the failure
394
+ // through the command handler so the user can fix the file intentionally.
395
+ console.debug(`[powerline-footer] Failed to parse settings at ${settingsPath}:`, error);
396
+ return null;
397
+ }
398
+ }
399
+
400
+ function readCompactionPolicyEnabled(configPath: string): boolean | undefined {
401
+ if (!existsSync(configPath)) return undefined;
402
+ try {
403
+ const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
404
+ if (!isRecord(parsed) || typeof parsed.enabled !== "boolean") return false;
405
+ return parsed.enabled;
406
+ } catch (error) {
407
+ console.debug(`[powerline-footer] Failed to read compaction policy from ${configPath}:`, error);
408
+ return false;
409
+ }
410
+ }
411
+
412
+ function detectCustomCompactionEnabled(cwd: string): boolean {
413
+ if (!existsSync(getCustomCompactionExtensionPath())) return false;
414
+
415
+ const projectSetting = readCompactionPolicyEnabled(join(cwd, ".pi", "compaction-policy.json"));
416
+ if (projectSetting !== undefined) return projectSetting;
417
+
418
+ return readCompactionPolicyEnabled(getGlobalCompactionPolicyPath()) ?? false;
419
+ }
420
+
421
+ function isRecord(value: unknown): value is Record<string, unknown> {
422
+ return typeof value === "object" && value !== null && !Array.isArray(value);
423
+ }
424
+
425
+ function getStashHistoryPath(): string {
426
+ const homeDir = process.env.HOME || process.env.USERPROFILE || homedir();
427
+ return join(homeDir, ".pi", "agent", "powerline-footer", "stash-history.json");
428
+ }
429
+
430
+ function getSessionsPath(): string {
431
+ const homeDir = process.env.HOME || process.env.USERPROFILE || homedir();
432
+ return join(homeDir, ".pi", "agent", "sessions");
433
+ }
434
+
435
+ function getProjectSessionsPath(cwd: string): string {
436
+ const projectKey = cwd
437
+ .replace(/^[/\\]+|[/\\]+$/g, "")
438
+ .replace(/[\\/]+/g, "-");
439
+
440
+ return join(getSessionsPath(), `--${projectKey}--`);
441
+ }
442
+
443
+ function getPromptHistoryText(content: unknown): string {
444
+ if (typeof content === "string") {
445
+ return content.replace(/\s+/g, " ").trim();
446
+ }
447
+
448
+ if (!Array.isArray(content)) {
449
+ return "";
450
+ }
451
+
452
+ const parts: string[] = [];
453
+ for (const block of content) {
454
+ if (!isRecord(block) || block.type !== "text" || typeof block.text !== "string") {
455
+ continue;
456
+ }
457
+ parts.push(block.text);
458
+ }
459
+
460
+ return parts.join("\n").replace(/\s+/g, " ").trim();
461
+ }
462
+
463
+ function readRecentProjectPrompts(cwd: string, limit: number): string[] {
464
+ const sessionsPath = getProjectSessionsPath(cwd);
465
+ if (!existsSync(sessionsPath)) {
466
+ return [];
467
+ }
468
+
469
+ const promptEntries: { text: string; timestamp: number }[] = [];
470
+ const fileNames = readdirSync(sessionsPath)
471
+ .filter((fileName) => fileName.endsWith(".jsonl"));
472
+
473
+ for (const fileName of fileNames) {
474
+ const filePath = join(sessionsPath, fileName);
475
+ const lines = readFileSync(filePath, "utf-8").split("\n");
476
+
477
+ for (let i = lines.length - 1; i >= 0; i--) {
478
+ const line = lines[i];
479
+ if (!line || !line.includes('"type":"message"') || !line.includes('"role":"user"')) {
480
+ continue;
481
+ }
482
+
483
+ let entry: unknown;
484
+ try {
485
+ entry = JSON.parse(line);
486
+ } catch (error) {
487
+ const message = error instanceof Error ? error.message : String(error);
488
+ throw new Error(`Failed to parse session file ${filePath}: ${message}`, { cause: error });
489
+ }
490
+
491
+ if (!isRecord(entry) || entry.type !== "message" || !isRecord(entry.message) || entry.message.role !== "user") {
492
+ continue;
493
+ }
494
+
495
+ const text = getPromptHistoryText(entry.message.content);
496
+ if (!hasNonWhitespaceText(text)) {
497
+ continue;
498
+ }
499
+
500
+ const timestamp = typeof entry.message.timestamp === "number"
501
+ ? entry.message.timestamp
502
+ : typeof entry.timestamp === "string"
503
+ ? Date.parse(entry.timestamp)
504
+ : 0;
505
+
506
+ promptEntries.push({ text, timestamp: Number.isFinite(timestamp) ? timestamp : 0 });
507
+ }
508
+ }
509
+
510
+ promptEntries.sort((a, b) => b.timestamp - a.timestamp);
511
+
512
+ const prompts: string[] = [];
513
+ const seen = new Set<string>();
514
+ for (const entry of promptEntries) {
515
+ if (seen.has(entry.text)) {
516
+ continue;
517
+ }
518
+
519
+ seen.add(entry.text);
520
+ prompts.push(entry.text);
521
+ if (prompts.length >= limit) {
522
+ return prompts;
523
+ }
524
+ }
525
+
526
+ return prompts;
527
+ }
528
+
529
+ function normalizeStashHistoryEntries(value: unknown): string[] {
530
+ if (!Array.isArray(value)) {
531
+ return [];
532
+ }
533
+
534
+ const history: string[] = [];
535
+ for (const entry of value) {
536
+ if (typeof entry !== "string") {
537
+ continue;
538
+ }
539
+
540
+ if (!hasNonWhitespaceText(entry)) {
541
+ continue;
542
+ }
543
+
544
+ if (history[history.length - 1] === entry) {
545
+ continue;
546
+ }
547
+
548
+ history.push(entry);
549
+ if (history.length >= STASH_HISTORY_LIMIT) {
550
+ break;
551
+ }
552
+ }
553
+
554
+ return history;
555
+ }
556
+
557
+ function readPersistedStashHistory(): string[] {
558
+ const stashHistoryPath = getStashHistoryPath();
559
+
560
+ try {
561
+ if (!existsSync(stashHistoryPath)) {
562
+ return [];
563
+ }
564
+
565
+ const parsed = JSON.parse(readFileSync(stashHistoryPath, "utf-8"));
566
+ if (!isRecord(parsed)) {
567
+ console.debug(`[powerline-footer] Ignoring invalid stash history at ${stashHistoryPath}`);
568
+ return [];
569
+ }
570
+
571
+ return normalizeStashHistoryEntries(parsed.history);
572
+ } catch (error) {
573
+ console.debug(`[powerline-footer] Failed to read stash history from ${stashHistoryPath}:`, error);
574
+ return [];
575
+ }
576
+ }
577
+
578
+ function persistStashHistory(history: string[]): void {
579
+ const stashHistoryPath = getStashHistoryPath();
580
+ const payload = {
581
+ version: 1,
582
+ history: history.slice(0, STASH_HISTORY_LIMIT),
583
+ };
584
+
585
+ try {
586
+ mkdirSync(dirname(stashHistoryPath), { recursive: true });
587
+ writeFileSync(stashHistoryPath, JSON.stringify(payload, null, 2) + "\n");
588
+ } catch (error) {
589
+ console.debug(`[powerline-footer] Failed to persist stash history to ${stashHistoryPath}:`, error);
590
+ }
591
+ }
592
+
593
+ function readSettings(cwd: string = process.cwd()): Record<string, unknown> {
594
+ return mergeSettings(readSettingsFile(getSettingsPath()), readSettingsFile(getProjectSettingsPath(cwd)));
595
+ }
596
+
597
+ function writePowerlineSetting(cwd: string, update: (existingPowerlineSetting: unknown) => unknown): boolean {
598
+ const globalSettingsPath = getSettingsPath();
599
+ const projectSettingsPath = getProjectSettingsPath(cwd);
600
+ const globalSettings = readWritableSettingsFile(globalSettingsPath);
601
+ const projectSettings = readWritableSettingsFile(projectSettingsPath);
602
+
603
+ if (globalSettings === null || projectSettings === null) {
604
+ return false;
605
+ }
606
+
607
+ const writeToProject = Object.prototype.hasOwnProperty.call(projectSettings, "powerline");
608
+ const settingsPath = writeToProject ? projectSettingsPath : globalSettingsPath;
609
+ const settings = writeToProject ? projectSettings : globalSettings;
610
+
611
+ settings.powerline = update(settings.powerline);
612
+
613
+ try {
614
+ mkdirSync(dirname(settingsPath), { recursive: true });
615
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
616
+ return true;
617
+ } catch (error) {
618
+ console.debug(`[powerline-footer] Failed to persist powerline setting to ${settingsPath}:`, error);
619
+ return false;
620
+ }
621
+ }
622
+
623
+ function writePowerlinePresetSetting(preset: StatusLinePreset, cwd: string = process.cwd()): boolean {
624
+ return writePowerlineSetting(cwd, (existingPowerlineSetting) => (
625
+ nextPowerlineSettingWithPreset(existingPowerlineSetting, preset)
626
+ ));
627
+ }
628
+
629
+ function writePowerlineOptionSetting(
630
+ cwd: string,
631
+ updates: Partial<Pick<PowerlineConfig, "mouseScroll" | "fixedEditor">>,
632
+ currentPreset: StatusLinePreset,
633
+ ): boolean {
634
+ return writePowerlineSetting(cwd, (existingPowerlineSetting) => (
635
+ nextPowerlineSettingWithOptions(existingPowerlineSetting, updates, currentPreset)
636
+ ));
637
+ }
638
+
639
+ const PRESET_NAMES = Object.keys(PRESETS) as StatusLinePreset[];
640
+
641
+ function isValidPreset(value: unknown): value is StatusLinePreset {
642
+ return typeof value === "string" && Object.prototype.hasOwnProperty.call(PRESETS, value);
643
+ }
644
+
645
+ function normalizePreset(value: unknown): StatusLinePreset | null {
646
+ if (typeof value !== "string") {
647
+ return null;
648
+ }
649
+
650
+ const preset = value.trim().toLowerCase();
651
+ return isValidPreset(preset) ? preset : null;
652
+ }
653
+
654
+ function hasNonWhitespaceText(text: string): boolean {
655
+ return text.trim().length > 0;
656
+ }
657
+
658
+ function getCurrentEditorText(ctx: any, editor: any): string {
659
+ return editor?.getExpandedText?.() ?? ctx.ui.getEditorText();
660
+ }
661
+
662
+ function buildStashPreview(text: string, maxWidth: number): string {
663
+ const compact = text.replace(/\s+/g, " ").trim();
664
+ if (!compact) return "(empty)";
665
+ return truncateToWidth(compact, maxWidth, "…");
666
+ }
667
+
668
+ function pushStashHistory(history: string[], text: string): boolean {
669
+ if (!hasNonWhitespaceText(text)) return false;
670
+ if (history[0] === text) return false;
671
+
672
+ history.unshift(text);
673
+ if (history.length > STASH_HISTORY_LIMIT) {
674
+ history.length = STASH_HISTORY_LIMIT;
675
+ }
676
+
677
+ return true;
678
+ }
679
+
680
+ function normalizeShortcut(value: string): string {
681
+ const parts = value.trim().toLowerCase().split("+");
682
+ if (parts.length <= 1) return parts[0] ?? "";
683
+
684
+ const modifierRank = new Map(SHORTCUT_MODIFIER_ORDER.map((modifier, index) => [modifier, index]));
685
+ const modifiers = parts.slice(0, -1).sort((a, b) => (modifierRank.get(a) ?? 99) - (modifierRank.get(b) ?? 99));
686
+ return [...modifiers, parts[parts.length - 1]].join("+");
687
+ }
688
+
689
+ function reservedShortcuts(): Set<string> {
690
+ const shortcuts = new Set<string>([
691
+ ...EXTRA_RESERVED_SHORTCUTS,
692
+ ...APP_RESERVED_SHORTCUTS,
693
+ ].map(normalizeShortcut));
694
+
695
+ for (const definition of Object.values(TUI_KEYBINDINGS)) {
696
+ const defaultKeys = definition.defaultKeys;
697
+ const keys = defaultKeys === undefined ? [] : Array.isArray(defaultKeys) ? defaultKeys : [defaultKeys];
698
+ for (const key of keys) {
699
+ shortcuts.add(normalizeShortcut(key));
700
+ }
701
+ }
702
+
703
+ return shortcuts;
704
+ }
705
+
706
+ function isValidShortcutKeyPart(keyPart: string): boolean {
707
+ const lowerKeyPart = keyPart.toLowerCase();
708
+
709
+ if (/^[a-z0-9]$/i.test(keyPart)) return true;
710
+ if (/^f([1-9]|1[0-2])$/i.test(keyPart)) return true;
711
+ if (SHORTCUT_NAMED_KEYS.has(lowerKeyPart)) return true;
712
+
713
+ return SHORTCUT_SYMBOL_KEYS.has(keyPart);
714
+ }
715
+
716
+ function parseShortcutOverride(value: unknown): string | null {
717
+ if (typeof value !== "string") {
718
+ return null;
719
+ }
720
+
721
+ const trimmed = value.trim();
722
+ if (!trimmed || /\s/.test(trimmed)) {
723
+ return null;
724
+ }
725
+
726
+ const parts = trimmed.split("+");
727
+ if (parts.some((part) => part.length === 0)) {
728
+ return null;
729
+ }
730
+
731
+ const modifierParts = parts.slice(0, -1).map((part) => {
732
+ const modifier = part.toLowerCase();
733
+ return modifier === "cmd" || modifier === "command" ? "super" : modifier;
734
+ });
735
+ if (new Set(modifierParts).size !== modifierParts.length) {
736
+ return null;
737
+ }
738
+
739
+ for (const modifier of modifierParts) {
740
+ if (!SHORTCUT_MODIFIERS.has(modifier)) {
741
+ return null;
742
+ }
743
+ }
744
+
745
+ const keyPart = parts[parts.length - 1];
746
+ if (!isValidShortcutKeyPart(keyPart)) {
747
+ return null;
748
+ }
749
+
750
+ const normalizedKey = SHORTCUT_SYMBOL_KEYS.has(keyPart) ? keyPart : keyPart.toLowerCase();
751
+ const normalizedShortcut = normalizeShortcut([...modifierParts, normalizedKey].join("+"));
752
+ if (shortcutUsesSuper(normalizedShortcut) && !isSupportedSuperShortcut(normalizedShortcut)) {
753
+ return null;
754
+ }
755
+
756
+ return normalizedShortcut;
757
+ }
758
+
759
+ function shortcutUsageKey(shortcut: string): string {
760
+ return shortcutConflictKey(normalizeShortcut(shortcut));
761
+ }
762
+
763
+ function findShortcutReplacement(key: PowerlineShortcutKey, used: Set<string>): string | null {
764
+ const preferred = DEFAULT_SHORTCUTS[key];
765
+ if (!used.has(shortcutUsageKey(preferred))) {
766
+ return preferred;
767
+ }
768
+
769
+ for (const shortcutKey of SHORTCUT_KEYS) {
770
+ const candidate = DEFAULT_SHORTCUTS[shortcutKey];
771
+ if (!used.has(shortcutUsageKey(candidate))) {
772
+ return candidate;
773
+ }
774
+ }
775
+
776
+ return null;
777
+ }
778
+
779
+ function resolveShortcutConfig(settings: Record<string, unknown>): PowerlineShortcuts {
780
+ const resolved: PowerlineShortcuts = { ...DEFAULT_SHORTCUTS };
781
+ const shortcutSettings = settings.powerlineShortcuts;
782
+
783
+ if (isRecord(shortcutSettings)) {
784
+ for (const key of SHORTCUT_KEYS) {
785
+ const override = parseShortcutOverride(shortcutSettings[key]);
786
+ if (override) {
787
+ resolved[key] = override;
788
+ }
789
+ }
790
+ }
791
+
792
+ const used = new Set(Array.from(reservedShortcuts(), shortcutUsageKey));
793
+
794
+ for (const key of SHORTCUT_KEYS) {
795
+ const configured = resolved[key];
796
+ const configuredUsageKey = shortcutUsageKey(configured);
797
+
798
+ if (!used.has(configuredUsageKey)) {
799
+ used.add(configuredUsageKey);
800
+ continue;
801
+ }
802
+
803
+ const replacement = findShortcutReplacement(key, used);
804
+ if (!replacement) {
805
+ console.debug(`[powerline-footer] Shortcut conflict for ${key}: "${configured}" is already in use`);
806
+ continue;
807
+ }
808
+
809
+ console.debug(
810
+ `[powerline-footer] Shortcut conflict for ${key}: "${configured}" replaced with "${replacement}"`,
811
+ );
812
+
813
+ resolved[key] = replacement;
814
+ used.add(shortcutUsageKey(replacement));
815
+ }
816
+
817
+ return resolved;
818
+ }
819
+
820
+ function parseBashModeSettings(settings: Record<string, unknown>): BashModeSettings {
821
+ const raw = isRecord(settings.bashMode) ? settings.bashMode : {};
822
+
823
+ const configuredToggleShortcut = parseShortcutOverride(raw.toggleShortcut);
824
+ const toggleShortcut = configuredToggleShortcut && !reservedShortcuts().has(shortcutUsageKey(configuredToggleShortcut))
825
+ ? configuredToggleShortcut
826
+ : DEFAULT_BASH_MODE_SETTINGS.toggleShortcut;
827
+
828
+ if (configuredToggleShortcut && toggleShortcut !== configuredToggleShortcut) {
829
+ console.debug(
830
+ `[powerline-footer] Bash mode shortcut conflict: "${configuredToggleShortcut}" replaced with "${toggleShortcut}"`,
831
+ );
832
+ }
833
+ const transcriptMaxLines = typeof raw.transcriptMaxLines === "number" && Number.isFinite(raw.transcriptMaxLines)
834
+ ? Math.max(100, Math.floor(raw.transcriptMaxLines))
835
+ : DEFAULT_BASH_MODE_SETTINGS.transcriptMaxLines;
836
+ const transcriptMaxBytes = typeof raw.transcriptMaxBytes === "number" && Number.isFinite(raw.transcriptMaxBytes)
837
+ ? Math.max(16 * 1024, Math.floor(raw.transcriptMaxBytes))
838
+ : DEFAULT_BASH_MODE_SETTINGS.transcriptMaxBytes;
839
+
840
+ return {
841
+ toggleShortcut,
842
+ transcriptMaxLines,
843
+ transcriptMaxBytes,
844
+ };
845
+ }
846
+
847
+ // ═══════════════════════════════════════════════════════════════════════════
848
+ // Status Line Builder
849
+ // ═══════════════════════════════════════════════════════════════════════════
850
+
851
+ /** Render a single segment and return its content with width */
852
+ function renderSegmentWithWidth(
853
+ segId: StatusLineSegmentId,
854
+ ctx: SegmentContext
855
+ ): { content: string; width: number; visible: boolean } {
856
+ const rendered = renderSegment(segId, ctx);
857
+ if (!rendered.visible || !rendered.content) {
858
+ return { content: "", width: 0, visible: false };
859
+ }
860
+ return { content: rendered.content, width: visibleWidth(rendered.content), visible: true };
861
+ }
862
+
863
+ /** Build content string from pre-rendered parts */
864
+ function buildContentFromParts(
865
+ parts: string[],
866
+ presetDef: ReturnType<typeof getPreset>
867
+ ): string {
868
+ if (parts.length === 0) return "";
869
+ const separatorDef = getSeparator(presetDef.separator);
870
+ const sepAnsi = getFgAnsiCode("sep");
871
+ const sep = separatorDef.left;
872
+ return " " + parts.join(`${sepAnsi}${sep}${ansi.reset}`) + ansi.reset + " ";
873
+ }
874
+
875
+ /**
876
+ * Responsive segment layout - fits segments into top bar, overflows to secondary row.
877
+ * When terminal is wide enough, secondary segments move up to top bar.
878
+ * When narrow, top bar segments overflow down to secondary row.
879
+ */
880
+ function computeResponsiveLayout(
881
+ ctx: SegmentContext,
882
+ presetDef: ReturnType<typeof getPreset>,
883
+ availableWidth: number
884
+ ): { topContent: string; secondaryContent: string } {
885
+ const separatorDef = getSeparator(presetDef.separator);
886
+ const sepWidth = visibleWidth(separatorDef.left); // separator, no surrounding spaces
887
+
888
+ const mergedSegments = mergeSegmentsWithCustomItems(presetDef, config.customItems);
889
+ const renderIds = (ids: StatusLineSegmentId[]): string[] => {
890
+ const parts: string[] = [];
891
+ for (const segId of ids) {
892
+ const { content, visible } = renderSegmentWithWidth(segId, ctx);
893
+ if (visible) parts.push(content);
894
+ }
895
+ return parts;
896
+ };
897
+
898
+ const leftParts = renderIds(mergedSegments.leftSegments);
899
+ const rightParts = renderIds(mergedSegments.rightSegments);
900
+ const secondaryParts = renderIds(mergedSegments.secondarySegments);
901
+
902
+ const leftContent = buildContentFromParts(leftParts, presetDef);
903
+ const rightContent = buildContentFromParts(rightParts, presetDef);
904
+ let topContent = leftContent;
905
+
906
+ if (rightContent) {
907
+ const gap = Math.max(0, availableWidth - visibleWidth(leftContent) - visibleWidth(rightContent));
908
+ topContent = leftContent
909
+ ? `${leftContent}${" ".repeat(gap)}${rightContent}`
910
+ : `${" ".repeat(gap)}${rightContent}`;
911
+ }
912
+
913
+ return {
914
+ topContent,
915
+ secondaryContent: buildContentFromParts(secondaryParts, presetDef),
916
+ };
917
+ }
918
+
919
+ // ═══════════════════════════════════════════════════════════════════════════
920
+ // Extension
921
+ // ═══════════════════════════════════════════════════════════════════════════
922
+
923
+ export default function powerlineFooter(pi: ExtensionAPI) {
924
+ const startupSettings = readSettings();
925
+ config = parsePowerlineConfig(startupSettings.powerline, PRESET_NAMES);
926
+ let resolvedShortcuts = resolveShortcutConfig(startupSettings);
927
+ let bashModeSettings = parseBashModeSettings(startupSettings);
928
+
929
+ let enabled = true;
930
+ let sessionStartTime = Date.now();
931
+ let sessionGeneration = 0;
932
+ let currentCtx: any = null;
933
+ let footerDataRef: ReadonlyFooterDataProvider | null = null;
934
+ let getThinkingLevelFn: (() => string) | null = null;
935
+ let currentThinkingLevel: string | null = null;
936
+ let liveAssistantUsage: SessionAssistantUsage | null = null;
937
+ let isStreaming = false;
938
+ let tuiRef: any = null;
939
+ let restoreFooterStatusRepaintHook: (() => void) | null = null;
940
+ let fixedEditorCompositor: TerminalSplitCompositor | null = null;
941
+ let fixedStatusContainer: any = null;
942
+ let fixedEditorContainer: any = null;
943
+ let fixedWidgetContainerAbove: any = null;
944
+ let fixedWidgetContainerBelow: any = null;
945
+ let stashShortcutInputUnsubscribe: (() => void) | null = null;
946
+ let dismissWelcomeOverlay: (() => void) | null = null;
947
+ let welcomeHeaderActive = false;
948
+ let welcomeOverlayShouldDismiss = false;
949
+ let lastUserPrompt = "";
950
+ let showLastPrompt = true;
951
+ let stashedEditorText: string | null = null;
952
+ let stashedPromptHistory: string[] = readPersistedStashHistory();
953
+ let currentEditor: any = null;
954
+ let bashModeActive = false;
955
+ let bashTranscript = new BashTranscriptStore(bashModeSettings);
956
+ let bashCompletionEngine = new BashCompletionEngine();
957
+ let shellSession: ManagedShellSession | null = null;
958
+
959
+ // Cache for the top and secondary powerline widgets.
960
+ let lastLayoutWidth = 0;
961
+ let lastLayoutResult: { topContent: string; secondaryContent: string } | null = null;
962
+ let lastLayoutTimestamp = 0;
963
+ let layoutDirty = true;
964
+ let forceNextLayoutRecompute = false;
965
+ let lastEditorInputAt = 0;
966
+ let lastLayoutSessionName: string | undefined;
967
+
968
+ const getShellPath = () => process.env.SHELL || "/bin/sh";
969
+ const getShellCwd = () => shellSession?.state.cwd ?? currentCtx?.cwd ?? process.cwd();
970
+ const welcomeDismissScheduler = createWelcomeDismissScheduler({
971
+ dismiss: (ctx: unknown) => dismissWelcome(ctx),
972
+ getGeneration: () => sessionGeneration,
973
+ isEnabled: () => enabled,
974
+ });
975
+
976
+ const statusRenderScheduler = createRenderScheduler(() => {
977
+ const msSinceInput = Date.now() - lastEditorInputAt;
978
+ if (layoutDirty && !forceNextLayoutRecompute && msSinceInput < EDITOR_STATUS_DEFER_MS) {
979
+ statusRenderScheduler.schedule(Math.max(0, EDITOR_STATUS_DEFER_MS - msSinceInput));
980
+ return;
981
+ }
982
+
983
+ tuiRef?.requestRender();
984
+ }, STATUS_RENDER_DEBOUNCE_MS);
985
+
986
+ const resetLayoutCache = () => {
987
+ lastLayoutResult = null;
988
+ lastLayoutSessionName = undefined;
989
+ layoutDirty = true;
990
+ };
991
+
992
+ const requestStatusRender = (delayMs?: number) => {
993
+ layoutDirty = true;
994
+ statusRenderScheduler.schedule(delayMs);
995
+ };
996
+
997
+ const requestImmediateStatusRender = (options: { deferDuringTyping?: boolean } = {}) => {
998
+ layoutDirty = true;
999
+ if (options.deferDuringTyping !== false && Date.now() - lastEditorInputAt < EDITOR_STATUS_DEFER_MS) {
1000
+ statusRenderScheduler.schedule();
1001
+ return;
1002
+ }
1003
+
1004
+ forceNextLayoutRecompute = true;
1005
+ statusRenderScheduler.cancel();
1006
+ statusRenderScheduler.schedule(0);
1007
+ };
1008
+
1009
+ const installFooterStatusRepaintHook = (footerData: ReadonlyFooterDataProvider) => {
1010
+ restoreFooterStatusRepaintHook?.();
1011
+ restoreFooterStatusRepaintHook = null;
1012
+
1013
+ const writableFooterData = footerData as ReadonlyFooterDataProvider & {
1014
+ setExtensionStatus?: (key: string, text: string | undefined) => void;
1015
+ clearExtensionStatuses?: () => void;
1016
+ };
1017
+ if (typeof writableFooterData.setExtensionStatus !== "function") return;
1018
+
1019
+ const originalSetExtensionStatus = writableFooterData.setExtensionStatus;
1020
+ const originalClearExtensionStatuses = writableFooterData.clearExtensionStatuses;
1021
+ const setExtensionStatusAndRepaint = function setExtensionStatusAndRepaint(this: unknown, key: string, text: string | undefined) {
1022
+ originalSetExtensionStatus.call(this, key, text);
1023
+ requestImmediateStatusRender();
1024
+ };
1025
+ writableFooterData.setExtensionStatus = setExtensionStatusAndRepaint;
1026
+
1027
+ let clearExtensionStatusesAndRepaint: (() => void) | null = null;
1028
+ if (typeof originalClearExtensionStatuses === "function") {
1029
+ clearExtensionStatusesAndRepaint = function clearExtensionStatusesAndRepaint(this: unknown) {
1030
+ originalClearExtensionStatuses.call(this);
1031
+ requestImmediateStatusRender();
1032
+ };
1033
+ writableFooterData.clearExtensionStatuses = clearExtensionStatusesAndRepaint;
1034
+ }
1035
+
1036
+ restoreFooterStatusRepaintHook = () => {
1037
+ if (writableFooterData.setExtensionStatus === setExtensionStatusAndRepaint) {
1038
+ writableFooterData.setExtensionStatus = originalSetExtensionStatus;
1039
+ }
1040
+ if (clearExtensionStatusesAndRepaint && writableFooterData.clearExtensionStatuses === clearExtensionStatusesAndRepaint) {
1041
+ writableFooterData.clearExtensionStatuses = originalClearExtensionStatuses;
1042
+ }
1043
+ };
1044
+ };
1045
+
1046
+ const getShellHistoryEntries = (prefix: string): string[] => {
1047
+ const project = matchHistoryEntries(
1048
+ readProjectHistory(currentCtx?.cwd ?? process.cwd()).map((entry) => entry.command),
1049
+ prefix,
1050
+ 50,
1051
+ );
1052
+ const global = matchHistoryEntries(readGlobalShellHistory(getShellPath()), prefix, 50);
1053
+ return [...new Set([...project, ...global])];
1054
+ };
1055
+
1056
+ const ensureShellSession = async (): Promise<ManagedShellSession> => {
1057
+ if (!shellSession) {
1058
+ shellSession = new ManagedShellSession(
1059
+ getShellPath(),
1060
+ currentCtx?.cwd ?? process.cwd(),
1061
+ bashTranscript,
1062
+ requestStatusRender,
1063
+ (command, cwd) => appendProjectHistory(currentCtx?.cwd ?? process.cwd(), command, cwd),
1064
+ );
1065
+ }
1066
+ await shellSession.ensureReady();
1067
+ return shellSession;
1068
+ };
1069
+
1070
+ const runShellCommand = async (command: string, ctx: any): Promise<void> => {
1071
+ try {
1072
+ const session = await ensureShellSession();
1073
+ await session.runCommand(command);
1074
+ requestStatusRender();
1075
+ } catch (error) {
1076
+ const message = error instanceof Error ? error.message : String(error);
1077
+ ctx.ui.notify(`Failed to run shell command: ${message}`, "error");
1078
+ }
1079
+ };
1080
+
1081
+ const setBashModeActive = async (value: boolean, ctx: any): Promise<void> => {
1082
+ if (value === bashModeActive) return;
1083
+ if (!value && shellSession?.state.running) {
1084
+ ctx.ui.notify("Wait for the current shell command to finish before leaving bash mode", "warning");
1085
+ return;
1086
+ }
1087
+
1088
+ if (value) {
1089
+ try {
1090
+ const session = await ensureShellSession();
1091
+ bashModeActive = true;
1092
+ currentEditor?.dismissBashModeUi?.();
1093
+ currentEditor?.refreshGhostSuggestion?.();
1094
+ requestStatusRender();
1095
+ ctx.ui.notify(`Bash mode enabled (${session.state.shellName})`, "info");
1096
+ } catch (error) {
1097
+ shellSession?.dispose();
1098
+ shellSession = null;
1099
+ bashModeActive = false;
1100
+ requestStatusRender();
1101
+ const message = error instanceof Error ? error.message : String(error);
1102
+ ctx.ui.notify(`Failed to start shell session: ${message}`, "error");
1103
+ }
1104
+ return;
1105
+ }
1106
+
1107
+ bashModeActive = value;
1108
+ currentEditor?.dismissBashModeUi?.();
1109
+ requestStatusRender();
1110
+ ctx.ui.notify("Bash mode disabled", "info");
1111
+ };
1112
+
1113
+ function overlaySelectListTheme(theme: Theme) {
1114
+ return {
1115
+ selectedPrefix: (text: string) => theme.fg("accent", text),
1116
+ selectedText: (text: string) => theme.fg("accent", text),
1117
+ description: (text: string) => theme.fg("muted", text),
1118
+ scrollInfo: (text: string) => theme.fg("dim", text),
1119
+ noMatch: (text: string) => theme.fg("warning", text),
1120
+ };
1121
+ }
1122
+
1123
+ async function showSelectOverlay(
1124
+ ctx: any,
1125
+ title: string,
1126
+ hint: string,
1127
+ items: SelectItem[],
1128
+ maxVisible: number,
1129
+ ): Promise<SelectItem | null> {
1130
+ return ctx.ui.custom<SelectItem | null>(
1131
+ (tui: any, theme: Theme, _keybindings: any, done: (result: SelectItem | null) => void) => {
1132
+ const selectList = new SelectList(items, maxVisible, overlaySelectListTheme(theme));
1133
+ const border = (text: string) => theme.fg("dim", text);
1134
+ const wrapRow = (text: string, innerWidth: number): string => {
1135
+ return `${border("│")}${truncateToWidth(text, innerWidth, "…", true)}${border("│")}`;
1136
+ };
1137
+
1138
+ selectList.onSelect = (item) => done(item);
1139
+ selectList.onCancel = () => done(null);
1140
+
1141
+ return {
1142
+ render: (width: number) => {
1143
+ const innerWidth = Math.max(1, width - 2);
1144
+ const lines: string[] = [];
1145
+
1146
+ lines.push(border(`╭${"─".repeat(innerWidth)}╮`));
1147
+ lines.push(wrapRow(theme.fg("accent", theme.bold(title)), innerWidth));
1148
+ lines.push(border(`├${"─".repeat(innerWidth)}┤`));
1149
+
1150
+ for (const line of selectList.render(innerWidth)) {
1151
+ lines.push(wrapRow(line, innerWidth));
1152
+ }
1153
+
1154
+ lines.push(border(`├${"─".repeat(innerWidth)}┤`));
1155
+ lines.push(wrapRow(theme.fg("dim", hint), innerWidth));
1156
+ lines.push(border(`╰${"─".repeat(innerWidth)}╯`));
1157
+
1158
+ return lines;
1159
+ },
1160
+ invalidate: () => selectList.invalidate(),
1161
+ handleInput: (data: string) => {
1162
+ selectList.handleInput(data);
1163
+ tui.requestRender();
1164
+ },
1165
+ };
1166
+ },
1167
+ {
1168
+ overlay: true,
1169
+ overlayOptions: () => ({
1170
+ verticalAlign: "center",
1171
+ horizontalAlign: "center",
1172
+ }),
1173
+ },
1174
+ );
1175
+ }
1176
+
1177
+ // Track session start
1178
+ pi.on("session_start", async (event, ctx) => {
1179
+ shellSession?.dispose();
1180
+ shellSession = null;
1181
+ sessionGeneration++;
1182
+ sessionStartTime = Date.now();
1183
+ currentCtx = ctx;
1184
+ customCompactionEnabled = detectCustomCompactionEnabled(ctx.cwd);
1185
+ lastUserPrompt = "";
1186
+ isStreaming = false;
1187
+ liveAssistantUsage = null;
1188
+ stashedEditorText = null;
1189
+
1190
+ const settings = readSettings(ctx.cwd);
1191
+ bashModeSettings = parseBashModeSettings(settings);
1192
+ resolvedShortcuts = resolveShortcutConfig(settings);
1193
+ showLastPrompt = settings.showLastPrompt !== false;
1194
+ config = parsePowerlineConfig(settings.powerline, PRESET_NAMES);
1195
+ stashedPromptHistory = readPersistedStashHistory();
1196
+ bashModeActive = false;
1197
+ bashTranscript = new BashTranscriptStore(bashModeSettings);
1198
+ bashCompletionEngine = new BashCompletionEngine();
1199
+
1200
+ getThinkingLevelFn = typeof ctx.getThinkingLevel === "function"
1201
+ ? () => ctx.getThinkingLevel()
1202
+ : null;
1203
+ currentThinkingLevel = getThinkingLevelFn?.() ?? null;
1204
+
1205
+ if (ctx.hasUI) {
1206
+ ctx.ui.setStatus("stash", undefined);
1207
+ }
1208
+
1209
+ // Initialize vibe manager (needs modelRegistry from ctx)
1210
+ initVibeManager(ctx);
1211
+
1212
+ if (enabled && ctx.hasUI) {
1213
+ setupCustomEditor(ctx);
1214
+ if (event.reason === "startup") {
1215
+ if (settings.quietStartup === true) {
1216
+ setupWelcomeHeader(ctx);
1217
+ } else {
1218
+ setupWelcomeOverlay(ctx);
1219
+ }
1220
+ } else {
1221
+ dismissWelcome(ctx);
1222
+ }
1223
+ }
1224
+
1225
+ });
1226
+
1227
+ pi.on("session_shutdown", async () => {
1228
+ sessionGeneration++;
1229
+ dismissWelcomeOverlay?.();
1230
+ dismissWelcomeOverlay = null;
1231
+ welcomeHeaderActive = false;
1232
+ welcomeOverlayShouldDismiss = false;
1233
+ welcomeDismissScheduler.cancel();
1234
+ statusRenderScheduler.cancel();
1235
+ restoreFooterStatusRepaintHook?.();
1236
+ restoreFooterStatusRepaintHook = null;
1237
+ teardownFixedEditorCompositor({ resetExtendedKeyboardModes: true });
1238
+ stashShortcutInputUnsubscribe?.();
1239
+ stashShortcutInputUnsubscribe = null;
1240
+ shellSession?.dispose();
1241
+ shellSession = null;
1242
+ bashModeActive = false;
1243
+ currentCtx = null;
1244
+ footerDataRef = null;
1245
+ getThinkingLevelFn = null;
1246
+ currentThinkingLevel = null;
1247
+ liveAssistantUsage = null;
1248
+ tuiRef = null;
1249
+ currentEditor = null;
1250
+ resetLayoutCache();
1251
+ });
1252
+
1253
+ // Check if a bash command might change git branch
1254
+ const mightChangeGitBranch = (cmd: string): boolean => {
1255
+ const gitBranchPatterns = [
1256
+ /\bgit\s+(checkout|switch|branch\s+-[dDmM]|merge|rebase|pull|reset|worktree)/,
1257
+ /\bgit\s+stash\s+(pop|apply)/,
1258
+ ];
1259
+ return gitBranchPatterns.some(p => p.test(cmd));
1260
+ };
1261
+
1262
+ // Invalidate git status on file changes, trigger re-render on potential branch changes
1263
+ pi.on("tool_result", async (event) => {
1264
+ if (event.toolName === "write" || event.toolName === "edit") {
1265
+ invalidateGitStatus();
1266
+ }
1267
+ // Check for bash commands that might change git branch
1268
+ if (event.toolName === "bash" && event.input?.command) {
1269
+ const cmd = String(event.input.command);
1270
+ if (mightChangeGitBranch(cmd)) {
1271
+ // Invalidate caches since working tree state changes with branch
1272
+ invalidateGitStatus();
1273
+ invalidateGitBranch();
1274
+ // Small delay to let git update, then re-render
1275
+ setTimeout(() => requestStatusRender(), 100);
1276
+ }
1277
+ }
1278
+ });
1279
+
1280
+ // Also catch user escape commands (! prefix)
1281
+ // Note: This fires BEFORE execution, so we use a longer delay and multiple re-renders
1282
+ // to ensure we catch the update after the command completes.
1283
+ pi.on("user_bash", async (event) => {
1284
+ if (mightChangeGitBranch(event.command)) {
1285
+ // Invalidate immediately so next render fetches fresh data
1286
+ invalidateGitStatus();
1287
+ invalidateGitBranch();
1288
+ // Multiple staggered re-renders to catch fast and slow commands
1289
+ setTimeout(() => requestStatusRender(), 100);
1290
+ setTimeout(() => requestStatusRender(), 300);
1291
+ setTimeout(() => requestStatusRender(), 500);
1292
+ }
1293
+ });
1294
+
1295
+ pi.on("model_select", async (_event, ctx) => {
1296
+ currentCtx = ctx;
1297
+ requestStatusRender();
1298
+ });
1299
+
1300
+ pi.on("thinking_level_select", async (event, ctx) => {
1301
+ currentCtx = ctx;
1302
+ currentThinkingLevel = getThinkingLevelFn?.() ?? (typeof event.level === "string" ? event.level : null);
1303
+ requestImmediateStatusRender({ deferDuringTyping: false });
1304
+ });
1305
+
1306
+ pi.on("session_tree", async (_event, ctx) => {
1307
+ currentCtx = ctx;
1308
+ currentThinkingLevel = null;
1309
+ liveAssistantUsage = null;
1310
+ requestImmediateStatusRender({ deferDuringTyping: false });
1311
+ });
1312
+
1313
+ // Generate themed working message before agent starts (has access to user's prompt)
1314
+ pi.on("before_agent_start", async (event, ctx) => {
1315
+ lastUserPrompt = event.prompt;
1316
+ if (ctx.hasUI) {
1317
+ onVibeBeforeAgentStart(event.prompt, ctx.ui.setWorkingMessage);
1318
+ }
1319
+ });
1320
+
1321
+ // Track streaming state (footer only shows status during streaming)
1322
+ // Also dismiss welcome when agent starts responding (handles `p "command"` case)
1323
+ pi.on("agent_start", async (_event, ctx) => {
1324
+ isStreaming = true;
1325
+ liveAssistantUsage = null;
1326
+ onVibeAgentStart();
1327
+ dismissWelcome(ctx);
1328
+ currentCtx = ctx;
1329
+ });
1330
+
1331
+ pi.on("message_update", async (event, ctx) => {
1332
+ if (isSessionAssistantMessage(event.message)
1333
+ && event.message.stopReason !== "error"
1334
+ && event.message.stopReason !== "aborted"
1335
+ && getUsageTokenTotal(event.message.usage) > 0) {
1336
+ liveAssistantUsage = event.message.usage;
1337
+ currentCtx = ctx;
1338
+ layoutDirty = true;
1339
+ statusRenderScheduler.schedule(CONTEXT_STATUS_RENDER_MS);
1340
+ }
1341
+ });
1342
+
1343
+ pi.on("message_end", async (event, ctx) => {
1344
+ currentCtx = ctx;
1345
+ if (isSessionAssistantMessage(event.message)) {
1346
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
1347
+ liveAssistantUsage = null;
1348
+ } else if (getUsageTokenTotal(event.message.usage) > 0) {
1349
+ liveAssistantUsage = event.message.usage;
1350
+ }
1351
+ }
1352
+ requestImmediateStatusRender({ deferDuringTyping: false });
1353
+ });
1354
+
1355
+ pi.on("turn_end", async (_event, ctx) => {
1356
+ currentCtx = ctx;
1357
+ requestImmediateStatusRender({ deferDuringTyping: false });
1358
+ });
1359
+
1360
+ // Also dismiss on tool calls (agent is working) + refresh vibe if rate limit allows
1361
+ pi.on("tool_call", async (event, ctx) => {
1362
+ dismissWelcome(ctx);
1363
+ if (ctx.hasUI) {
1364
+ // Extract recent agent context from session for richer vibe generation
1365
+ const agentContext = getRecentAgentContext(ctx);
1366
+ onVibeToolCall(event.toolName, event.input, ctx.ui.setWorkingMessage, agentContext);
1367
+ }
1368
+ });
1369
+
1370
+ // Helper to extract recent agent response text (skipping thinking blocks)
1371
+ function getRecentAgentContext(ctx: any): string | undefined {
1372
+ const sessionEvents = ctx.sessionManager?.getBranch?.() ?? [];
1373
+
1374
+ // Find the most recent assistant message
1375
+ for (let i = sessionEvents.length - 1; i >= 0; i--) {
1376
+ const e = sessionEvents[i];
1377
+ if (e.type === "message" && e.message?.role === "assistant") {
1378
+ const content = e.message.content;
1379
+ if (!Array.isArray(content)) continue;
1380
+
1381
+ // Extract text content, skip thinking blocks
1382
+ for (const block of content) {
1383
+ if (block.type === "text" && block.text) {
1384
+ // Return first ~200 chars of non-empty text
1385
+ const text = block.text.trim();
1386
+ if (text.length > 0) {
1387
+ return text.slice(0, 200);
1388
+ }
1389
+ }
1390
+ }
1391
+ }
1392
+ }
1393
+ return undefined;
1394
+ }
1395
+
1396
+ function dismissWelcome(ctx: any) {
1397
+ welcomeDismissScheduler.cancel();
1398
+
1399
+ if (dismissWelcomeOverlay) {
1400
+ dismissWelcomeOverlay();
1401
+ dismissWelcomeOverlay = null;
1402
+ } else {
1403
+ // The startup overlay mounts after a delay; dismiss it immediately if it appears later.
1404
+ welcomeOverlayShouldDismiss = true;
1405
+ }
1406
+ if (welcomeHeaderActive) {
1407
+ welcomeHeaderActive = false;
1408
+ ctx.ui.setHeader(undefined);
1409
+ }
1410
+ }
1411
+
1412
+ function scheduleDismissWelcome(ctx: any) {
1413
+ if (!dismissWelcomeOverlay && welcomeOverlayShouldDismiss && !welcomeHeaderActive) return;
1414
+ welcomeDismissScheduler.schedule(ctx);
1415
+ }
1416
+
1417
+ function addStashHistoryEntry(text: string): void {
1418
+ const changed = pushStashHistory(stashedPromptHistory, text);
1419
+ if (!changed) {
1420
+ return;
1421
+ }
1422
+
1423
+ persistStashHistory(stashedPromptHistory);
1424
+ }
1425
+
1426
+ function copyTextToClipboard(ctx: any, text: string, successMessage?: string): void {
1427
+ copyToClipboard(text);
1428
+ if (successMessage) {
1429
+ ctx.ui.notify(successMessage, "info");
1430
+ }
1431
+ }
1432
+
1433
+ function getEditorTextForClipboard(ctx: any): string | null {
1434
+ const text = getCurrentEditorText(ctx, currentEditor);
1435
+ if (hasNonWhitespaceText(text)) {
1436
+ return text;
1437
+ }
1438
+
1439
+ ctx.ui.notify("Editor is empty", "info");
1440
+ return null;
1441
+ }
1442
+
1443
+ async function selectStashedPromptFromHistory(ctx: any): Promise<string | null> {
1444
+ const historyItems = [...stashedPromptHistory];
1445
+ const items: SelectItem[] = historyItems.map((entry, index) => ({
1446
+ value: String(index),
1447
+ label: `#${index + 1} ${buildStashPreview(entry, STASH_PREVIEW_WIDTH)}`,
1448
+ }));
1449
+
1450
+ const selected = await showSelectOverlay(
1451
+ ctx, "Stash history", "↑↓ navigate • enter insert • esc cancel",
1452
+ items, Math.min(items.length, 10));
1453
+ if (!selected) return null;
1454
+
1455
+ const i = Number.parseInt(selected.value, 10);
1456
+ return historyItems[i] ?? null;
1457
+ }
1458
+
1459
+ async function selectProjectPromptFromHistory(ctx: any, prompts: string[]): Promise<string | null> {
1460
+ const items: SelectItem[] = prompts.map((entry, index) => ({
1461
+ value: String(index),
1462
+ label: `#${index + 1} ${buildStashPreview(entry, STASH_PREVIEW_WIDTH)}`,
1463
+ }));
1464
+
1465
+ const selected = await showSelectOverlay(
1466
+ ctx, "Recent project prompts", "↑↓ navigate • enter insert • esc cancel",
1467
+ items, Math.min(items.length, 10));
1468
+ if (!selected) return null;
1469
+
1470
+ const i = Number.parseInt(selected.value, 10);
1471
+ return prompts[i] ?? null;
1472
+ }
1473
+
1474
+ async function selectPromptHistorySource(
1475
+ ctx: any,
1476
+ stashCount: number,
1477
+ projectPromptCount: number,
1478
+ ): Promise<"stash" | "project" | null> {
1479
+ const items: SelectItem[] = [];
1480
+
1481
+ if (stashCount > 0) {
1482
+ items.push({
1483
+ value: "stash",
1484
+ label: "Stashed prompts",
1485
+ description: `${stashCount} saved`,
1486
+ });
1487
+ }
1488
+
1489
+ if (projectPromptCount > 0) {
1490
+ items.push({
1491
+ value: "project",
1492
+ label: "Recent project prompts",
1493
+ description: `${projectPromptCount} recent`,
1494
+ });
1495
+ }
1496
+
1497
+ if (items.length === 0) {
1498
+ return null;
1499
+ }
1500
+
1501
+ if (items.length === 1) {
1502
+ return items[0]?.value === "project" ? "project" : "stash";
1503
+ }
1504
+
1505
+ const selected = await showSelectOverlay(
1506
+ ctx, "Prompt history", "↑↓ navigate • enter open • esc cancel",
1507
+ items, items.length);
1508
+ if (!selected) return null;
1509
+
1510
+ return selected.value === "project" ? "project" : "stash";
1511
+ }
1512
+
1513
+ async function insertSelectedPromptHistoryEntry(ctx: any, selected: string): Promise<void> {
1514
+ const currentText = getCurrentEditorText(ctx, currentEditor);
1515
+ if (!hasNonWhitespaceText(currentText)) {
1516
+ ctx.ui.setEditorText(selected);
1517
+ ctx.ui.notify("Inserted prompt", "info");
1518
+ return;
1519
+ }
1520
+
1521
+ const action = await ctx.ui.select("Insert prompt", ["Replace", "Append", "Cancel"]);
1522
+
1523
+ if (action === "Replace") {
1524
+ ctx.ui.setEditorText(selected);
1525
+ ctx.ui.notify("Replaced editor with prompt", "info");
1526
+ return;
1527
+ }
1528
+
1529
+ if (action === "Append") {
1530
+ const separator = currentText.endsWith("\n") || selected.startsWith("\n") ? "" : "\n";
1531
+ ctx.ui.setEditorText(`${currentText}${separator}${selected}`);
1532
+ ctx.ui.notify("Appended prompt", "info");
1533
+ }
1534
+ }
1535
+
1536
+ function isStashShortcutInput(data: string): boolean {
1537
+ if (isKeyRelease(data)) return false;
1538
+
1539
+ return data === "ß"
1540
+ || data === "\x1bs"
1541
+ || data === "\x1bS"
1542
+ || /^\x1b\[(?:83|115)(?::\d*)?(?::\d*)?;3(?::\d+)?u$/.test(data)
1543
+ || data === "\x1b[27;3;115~"
1544
+ || data === "\x1b[27;3;83~"
1545
+ || matchesKey(data, "alt+s");
1546
+ }
1547
+
1548
+ function getChatJumpShortcutAction(data: string): ChatJumpShortcutAction | null {
1549
+ return CHAT_JUMP_SHORTCUTS.find(({ shortcutKey }) => matchesConfiguredShortcut(data, resolvedShortcuts[shortcutKey]))?.action ?? null;
1550
+ }
1551
+
1552
+ function isPromptHistoryShortcutInput(data: string): boolean {
1553
+ return matchesConfiguredShortcut(data, resolvedShortcuts.stashHistory)
1554
+ || (resolvedShortcuts.stashHistory === "ctrl+alt+h" && (
1555
+ /^\x1b\[104(?::\d*)?(?::\d*)?;7(?::\d+)?u$/.test(data)
1556
+ || data === "\x1b[27;7;104~"
1557
+ || data === "\x1b[27;7;72~"
1558
+ ));
1559
+ }
1560
+
1561
+ function getPowerlineShortcutAction(data: string): PowerlineShortcutAction | null {
1562
+ if (isKeyRelease(data)) return null;
1563
+
1564
+ if (isPromptHistoryShortcutInput(data)) {
1565
+ return { kind: "stashHistory" };
1566
+ }
1567
+ if (matchesConfiguredShortcut(data, resolvedShortcuts.copyEditor)) {
1568
+ return { kind: "copyEditor" };
1569
+ }
1570
+ if (matchesConfiguredShortcut(data, resolvedShortcuts.cutEditor)) {
1571
+ return { kind: "cutEditor" };
1572
+ }
1573
+ if (matchesConfiguredShortcut(data, bashModeSettings.toggleShortcut)) {
1574
+ return { kind: "bashMode" };
1575
+ }
1576
+
1577
+ const chatJumpAction = getChatJumpShortcutAction(data);
1578
+ return chatJumpAction ? { kind: "chat", action: chatJumpAction } : null;
1579
+ }
1580
+
1581
+ function runPowerlineShortcut(ctx: any, action: PowerlineShortcutAction): void {
1582
+ if (action.kind === "stashHistory") {
1583
+ void openStashHistory(ctx);
1584
+ return;
1585
+ }
1586
+
1587
+ if (action.kind === "copyEditor" || action.kind === "cutEditor") {
1588
+ const text = getEditorTextForClipboard(ctx);
1589
+ if (!text) return;
1590
+
1591
+ copyTextToClipboard(ctx, text, action.kind === "copyEditor" ? "Copied editor text" : undefined);
1592
+ if (action.kind === "cutEditor") {
1593
+ ctx.ui.setEditorText("");
1594
+ ctx.ui.notify("Cut editor text", "info");
1595
+ }
1596
+ return;
1597
+ }
1598
+
1599
+ if (action.kind === "bashMode") {
1600
+ void setBashModeActive(!bashModeActive, ctx);
1601
+ return;
1602
+ }
1603
+
1604
+ if (action.action.kind === "bottom") {
1605
+ jumpChatToBottom(ctx);
1606
+ return;
1607
+ }
1608
+
1609
+ jumpToChatMessage(ctx, action.action.role, action.action.direction);
1610
+ }
1611
+
1612
+ function stashOrRestoreEditorText(ctx: any): void {
1613
+ const rawText = getCurrentEditorText(ctx, currentEditor);
1614
+ const hasStash = stashedEditorText !== null;
1615
+
1616
+ if (!hasNonWhitespaceText(rawText)) {
1617
+ if (!hasStash) {
1618
+ ctx.ui.notify("Nothing to stash", "info");
1619
+ return;
1620
+ }
1621
+
1622
+ ctx.ui.setEditorText(stashedEditorText);
1623
+ stashedEditorText = null;
1624
+ ctx.ui.setStatus("stash", undefined);
1625
+ ctx.ui.notify("Stash restored", "info");
1626
+ return;
1627
+ }
1628
+
1629
+ stashedEditorText = rawText;
1630
+ addStashHistoryEntry(rawText);
1631
+ ctx.ui.setEditorText("");
1632
+ ctx.ui.setStatus("stash", "stash");
1633
+ ctx.ui.notify(hasStash ? "Stash updated" : "Text stashed", "info");
1634
+ }
1635
+
1636
+ async function openStashHistory(ctx: any): Promise<void> {
1637
+ let projectPrompts: string[] = [];
1638
+
1639
+ try {
1640
+ projectPrompts = readRecentProjectPrompts(ctx.cwd, PROJECT_PROMPT_HISTORY_LIMIT);
1641
+ } catch (error) {
1642
+ const message = error instanceof Error ? error.message : String(error);
1643
+ ctx.ui.notify(`Failed to load project prompts: ${message}`, "warning");
1644
+ }
1645
+
1646
+ if (stashedPromptHistory.length === 0 && projectPrompts.length === 0) {
1647
+ ctx.ui.notify("No prompt history yet", "info");
1648
+ return;
1649
+ }
1650
+
1651
+ const source = await selectPromptHistorySource(ctx, stashedPromptHistory.length, projectPrompts.length);
1652
+ if (!source) {
1653
+ return;
1654
+ }
1655
+
1656
+ const selected = source === "project"
1657
+ ? await selectProjectPromptFromHistory(ctx, projectPrompts)
1658
+ : await selectStashedPromptFromHistory(ctx);
1659
+ if (!selected) return;
1660
+
1661
+ await insertSelectedPromptHistoryEntry(ctx, selected);
1662
+ }
1663
+
1664
+ pi.on("agent_end", async (_event, ctx) => {
1665
+ isStreaming = false;
1666
+ liveAssistantUsage = null;
1667
+ currentCtx = ctx;
1668
+ if (ctx.hasUI) {
1669
+ onVibeAgentEnd(ctx.ui.setWorkingMessage); // working-vibes internal state + reset message
1670
+ if (stashedEditorText !== null) {
1671
+ if (ctx.ui.getEditorText().trim() === "") {
1672
+ ctx.ui.setEditorText(stashedEditorText);
1673
+ stashedEditorText = null;
1674
+ ctx.ui.setStatus("stash", undefined);
1675
+ ctx.ui.notify("Stash restored", "info");
1676
+ } else {
1677
+ ctx.ui.notify("Stash preserved — clear editor then Alt+S to restore", "info");
1678
+ }
1679
+ }
1680
+ }
1681
+ requestStatusRender();
1682
+ });
1683
+
1684
+ // Command to toggle/configure
1685
+ pi.registerCommand("powerline", {
1686
+ description: "Configure powerline status (toggle, preset)",
1687
+ handler: async (args, ctx) => {
1688
+ // Update context reference (command ctx may have more methods)
1689
+ currentCtx = ctx;
1690
+
1691
+ if (!args?.trim()) {
1692
+ // Toggle
1693
+ enabled = !enabled;
1694
+ if (enabled) {
1695
+ setupCustomEditor(ctx);
1696
+ ctx.ui.notify("Powerline enabled", "info");
1697
+ } else {
1698
+ shellSession?.dispose();
1699
+ shellSession = null;
1700
+ bashTranscript.clear();
1701
+ bashModeActive = false;
1702
+ dismissWelcomeOverlay?.();
1703
+ dismissWelcomeOverlay = null;
1704
+ welcomeHeaderActive = false;
1705
+ welcomeOverlayShouldDismiss = false;
1706
+ welcomeDismissScheduler.cancel();
1707
+ getPromptHistoryState().savedPromptHistory = [];
1708
+ stashedEditorText = null;
1709
+ ctx.ui.setStatus("stash", undefined);
1710
+ restoreFooterStatusRepaintHook?.();
1711
+ restoreFooterStatusRepaintHook = null;
1712
+ teardownFixedEditorCompositor();
1713
+ stashShortcutInputUnsubscribe?.();
1714
+ stashShortcutInputUnsubscribe = null;
1715
+ // Clear all custom UI components
1716
+ ctx.ui.setEditorComponent(undefined);
1717
+ ctx.ui.setFooter(undefined);
1718
+ ctx.ui.setHeader(undefined);
1719
+ ctx.ui.setWidget("powerline-top", undefined);
1720
+ ctx.ui.setWidget("powerline-secondary", undefined);
1721
+ ctx.ui.setWidget("powerline-bash-transcript", undefined);
1722
+ ctx.ui.setWidget("powerline-status", undefined);
1723
+ ctx.ui.setWidget("powerline-last-prompt", undefined);
1724
+ footerDataRef = null;
1725
+ tuiRef = null;
1726
+ currentEditor = null;
1727
+ statusRenderScheduler.cancel();
1728
+ resetLayoutCache();
1729
+ ctx.ui.notify("Powerline disabled", "info");
1730
+ }
1731
+ return;
1732
+ }
1733
+
1734
+ const normalizedArgs = args.trim().toLowerCase();
1735
+ const mouseScrollMatch = /^mouse-scroll(?:\s+(on|off|toggle))?$/.exec(normalizedArgs);
1736
+ if (mouseScrollMatch) {
1737
+ const mode = mouseScrollMatch[1] ?? "toggle";
1738
+ config.mouseScroll = mode === "toggle" ? !config.mouseScroll : mode === "on";
1739
+ if (enabled && ctx.hasUI && config.fixedEditor && tuiRef && currentEditor) {
1740
+ installFixedEditorCompositor(ctx, tuiRef);
1741
+ }
1742
+
1743
+ if (writePowerlineOptionSetting(ctx.cwd, { mouseScroll: config.mouseScroll }, config.preset)) {
1744
+ ctx.ui.notify(`Powerline mouse scroll ${config.mouseScroll ? "enabled" : "disabled"}`, "info");
1745
+ } else {
1746
+ ctx.ui.notify(`Powerline mouse scroll ${config.mouseScroll ? "enabled" : "disabled"} (not persisted; check settings.json)`, "warning");
1747
+ }
1748
+ return;
1749
+ }
1750
+
1751
+ const fixedEditorMatch = /^fixed-editor(?:\s+(on|off|toggle))?$/.exec(normalizedArgs);
1752
+ if (fixedEditorMatch) {
1753
+ const mode = fixedEditorMatch[1] ?? "toggle";
1754
+ config.fixedEditor = mode === "toggle" ? !config.fixedEditor : mode === "on";
1755
+ if (enabled && ctx.hasUI) {
1756
+ setupCustomEditor(ctx);
1757
+ }
1758
+
1759
+ if (writePowerlineOptionSetting(ctx.cwd, { fixedEditor: config.fixedEditor }, config.preset)) {
1760
+ ctx.ui.notify(`Powerline fixed editor ${config.fixedEditor ? "enabled" : "disabled"}`, "info");
1761
+ } else {
1762
+ ctx.ui.notify(`Powerline fixed editor ${config.fixedEditor ? "enabled" : "disabled"} (not persisted; check settings.json)`, "warning");
1763
+ }
1764
+ return;
1765
+ }
1766
+
1767
+ const preset = normalizePreset(args);
1768
+ if (preset) {
1769
+ config.preset = preset;
1770
+ resetLayoutCache();
1771
+ if (enabled) {
1772
+ setupCustomEditor(ctx);
1773
+ }
1774
+
1775
+ if (writePowerlinePresetSetting(preset, ctx.cwd)) {
1776
+ ctx.ui.notify(`Preset set to: ${preset}`, "info");
1777
+ } else {
1778
+ ctx.ui.notify(`Preset set to: ${preset} (not persisted; check settings.json)`, "warning");
1779
+ }
1780
+ return;
1781
+ }
1782
+
1783
+ // Show available presets
1784
+ const presetList = Object.keys(PRESETS).join(", ");
1785
+ ctx.ui.notify(`Available presets: ${presetList}`, "info");
1786
+ },
1787
+ });
1788
+
1789
+ pi.registerCommand("stash-history", {
1790
+ description: "Open prompt history picker",
1791
+ handler: async (_args, ctx) => {
1792
+ if (!ctx.hasUI) return;
1793
+ if (!enabled) {
1794
+ ctx.ui.notify("Powerline is disabled", "info");
1795
+ return;
1796
+ }
1797
+
1798
+ await openStashHistory(ctx);
1799
+ },
1800
+ });
1801
+
1802
+ pi.registerCommand("bash-mode", {
1803
+ description: "Toggle sticky bash mode (on, off, toggle)",
1804
+ handler: async (args, ctx) => {
1805
+ const mode = args?.trim().toLowerCase() || "toggle";
1806
+ if (mode === "on") {
1807
+ await setBashModeActive(true, ctx);
1808
+ return;
1809
+ }
1810
+ if (mode === "off") {
1811
+ await setBashModeActive(false, ctx);
1812
+ return;
1813
+ }
1814
+ if (mode === "toggle") {
1815
+ await setBashModeActive(!bashModeActive, ctx);
1816
+ return;
1817
+ }
1818
+ ctx.ui.notify("Usage: /bash-mode [on|off|toggle]", "warning");
1819
+ },
1820
+ });
1821
+
1822
+ pi.registerCommand("bash-reset", {
1823
+ description: "Reset the managed bash session",
1824
+ handler: async (_args, ctx) => {
1825
+ shellSession?.dispose();
1826
+ shellSession = null;
1827
+ bashTranscript.clear();
1828
+ if (bashModeActive) {
1829
+ try {
1830
+ await ensureShellSession();
1831
+ } catch (error) {
1832
+ bashModeActive = false;
1833
+ const message = error instanceof Error ? error.message : String(error);
1834
+ ctx.ui.notify(`Failed to restart shell session: ${message}`, "error");
1835
+ requestStatusRender();
1836
+ return;
1837
+ }
1838
+ }
1839
+ requestStatusRender();
1840
+ ctx.ui.notify("Bash session reset", "info");
1841
+ },
1842
+ });
1843
+
1844
+ pi.registerShortcut(bashModeSettings.toggleShortcut, {
1845
+ description: "Toggle bash mode",
1846
+ handler: async (ctx) => {
1847
+ if (!enabled || !ctx.hasUI) return;
1848
+ await setBashModeActive(!bashModeActive, ctx);
1849
+ },
1850
+ });
1851
+
1852
+ pi.registerShortcut("alt+s", {
1853
+ description: "Stash/restore editor text",
1854
+ handler: async (ctx) => {
1855
+ if (!enabled || !ctx.hasUI) return;
1856
+ stashOrRestoreEditorText(ctx);
1857
+ },
1858
+ });
1859
+
1860
+ pi.registerShortcut(resolvedShortcuts.stashHistory, {
1861
+ description: "Open prompt history picker",
1862
+ handler: async (ctx) => {
1863
+ if (!enabled || !ctx.hasUI) return;
1864
+ await openStashHistory(ctx);
1865
+ },
1866
+ });
1867
+
1868
+ pi.registerShortcut(resolvedShortcuts.copyEditor, {
1869
+ description: "Copy full editor text",
1870
+ handler: async (ctx) => {
1871
+ if (!enabled || !ctx.hasUI) return;
1872
+
1873
+ const text = getEditorTextForClipboard(ctx);
1874
+ if (!text) return;
1875
+
1876
+ copyTextToClipboard(ctx, text, "Copied editor text");
1877
+ },
1878
+ });
1879
+
1880
+ pi.registerShortcut(resolvedShortcuts.cutEditor, {
1881
+ description: "Cut full editor text",
1882
+ handler: async (ctx) => {
1883
+ if (!enabled || !ctx.hasUI) return;
1884
+
1885
+ const text = getEditorTextForClipboard(ctx);
1886
+ if (!text) return;
1887
+
1888
+ copyTextToClipboard(ctx, text);
1889
+ ctx.ui.setEditorText("");
1890
+ ctx.ui.notify("Cut editor text", "info");
1891
+ },
1892
+ });
1893
+
1894
+ for (const { shortcutKey, description, action } of CHAT_JUMP_SHORTCUTS) {
1895
+ pi.registerShortcut(resolvedShortcuts[shortcutKey], {
1896
+ description,
1897
+ handler: async (ctx) => {
1898
+ if (!enabled || !ctx.hasUI) return;
1899
+ runPowerlineShortcut(ctx, { kind: "chat", action });
1900
+ },
1901
+ });
1902
+ }
1903
+
1904
+ // Command to set working message theme
1905
+ pi.registerCommand("vibe", {
1906
+ description: "Set working message theme. Usage: /vibe [theme|off|mode|model|generate]",
1907
+ handler: async (args, ctx) => {
1908
+ const parts = args?.trim().split(/\s+/) || [];
1909
+ const subcommand = parts[0]?.toLowerCase();
1910
+
1911
+ // No args: show current status
1912
+ if (!args || !args.trim()) {
1913
+ const theme = getVibeTheme();
1914
+ const mode = getVibeMode();
1915
+ const model = getVibeModel();
1916
+ let status = `Vibe: ${theme || "off"} | Mode: ${mode} | Model: ${model}`;
1917
+ if (theme && mode === "file") {
1918
+ const count = getVibeFileCount(theme);
1919
+ status += count > 0 ? ` | File: ${count} vibes` : " | File: not found";
1920
+ }
1921
+ ctx.ui.notify(status, "info");
1922
+ return;
1923
+ }
1924
+
1925
+ // /vibe model [spec] - show or set model
1926
+ if (subcommand === "model") {
1927
+ const modelSpec = parts.slice(1).join(" ");
1928
+ if (!modelSpec) {
1929
+ ctx.ui.notify(`Current vibe model: ${getVibeModel()}`, "info");
1930
+ return;
1931
+ }
1932
+ // Validate format (provider/modelId)
1933
+ if (!modelSpec.includes("/")) {
1934
+ ctx.ui.notify("Invalid model format. Use: provider/modelId (e.g., openai-codex/gpt-5.4-mini)", "error");
1935
+ return;
1936
+ }
1937
+ const persisted = setVibeModel(modelSpec);
1938
+ if (persisted) {
1939
+ ctx.ui.notify(`Vibe model set to: ${modelSpec}`, "info");
1940
+ } else {
1941
+ ctx.ui.notify(`Vibe model set to: ${modelSpec} (not persisted; check settings.json)`, "warning");
1942
+ }
1943
+ return;
1944
+ }
1945
+
1946
+ // /vibe mode [generate|file] - show or set mode
1947
+ if (subcommand === "mode") {
1948
+ const newMode = parts[1]?.toLowerCase();
1949
+ if (!newMode) {
1950
+ ctx.ui.notify(`Current vibe mode: ${getVibeMode()}`, "info");
1951
+ return;
1952
+ }
1953
+ if (newMode !== "generate" && newMode !== "file") {
1954
+ ctx.ui.notify("Invalid mode. Use: generate or file", "error");
1955
+ return;
1956
+ }
1957
+ // Check if file exists when switching to file mode
1958
+ const theme = getVibeTheme();
1959
+ if (newMode === "file" && theme && !hasVibeFile(theme)) {
1960
+ ctx.ui.notify(`No vibe file for "${theme}". Run /vibe generate ${theme} first`, "error");
1961
+ return;
1962
+ }
1963
+ const persisted = setVibeMode(newMode);
1964
+ if (persisted) {
1965
+ ctx.ui.notify(`Vibe mode set to: ${newMode}`, "info");
1966
+ } else {
1967
+ ctx.ui.notify(`Vibe mode set to: ${newMode} (not persisted; check settings.json)`, "warning");
1968
+ }
1969
+ return;
1970
+ }
1971
+
1972
+ // /vibe generate <theme> [count] - generate vibes and save to file
1973
+ if (subcommand === "generate") {
1974
+ const theme = parts[1];
1975
+ const parsedCount = Number.parseInt(parts[2] ?? "", 10);
1976
+ const count = Number.isFinite(parsedCount)
1977
+ ? Math.min(Math.max(Math.floor(parsedCount), 1), 500)
1978
+ : 100;
1979
+
1980
+ if (!theme) {
1981
+ ctx.ui.notify("Usage: /vibe generate <theme> [count]", "error");
1982
+ return;
1983
+ }
1984
+
1985
+ ctx.ui.notify(`Generating ${count} vibes for "${theme}"...`, "info");
1986
+
1987
+ const result = await generateVibesBatch(theme, count);
1988
+
1989
+ if (result.success) {
1990
+ ctx.ui.notify(`Generated ${result.count} vibes for "${theme}" → ${result.filePath}`, "info");
1991
+ } else {
1992
+ ctx.ui.notify(`Failed to generate vibes: ${result.error}`, "error");
1993
+ }
1994
+ return;
1995
+ }
1996
+
1997
+ // /vibe off - disable
1998
+ if (subcommand === "off") {
1999
+ const persisted = setVibeTheme(null);
2000
+ if (persisted) {
2001
+ ctx.ui.notify("Vibe disabled", "info");
2002
+ } else {
2003
+ ctx.ui.notify("Vibe disabled (not persisted; check settings.json)", "warning");
2004
+ }
2005
+ return;
2006
+ }
2007
+
2008
+ // /vibe <theme> - set theme (preserve original casing)
2009
+ const theme = args.trim();
2010
+ const persisted = setVibeTheme(theme);
2011
+ const mode = getVibeMode();
2012
+ if (mode === "file" && !hasVibeFile(theme)) {
2013
+ const suffix = persisted ? "" : " (not persisted; check settings.json)";
2014
+ ctx.ui.notify(`Vibe set to: ${theme} (file mode, but no file found - run /vibe generate ${theme})${suffix}`, "warning");
2015
+ } else if (persisted) {
2016
+ ctx.ui.notify(`Vibe set to: ${theme}`, "info");
2017
+ } else {
2018
+ ctx.ui.notify(`Vibe set to: ${theme} (not persisted; check settings.json)`, "warning");
2019
+ }
2020
+ },
2021
+ });
2022
+
2023
+ function getCurrentSessionName(ctx: any): string | undefined {
2024
+ const direct = pi.getSessionName?.() ?? ctx?.sessionManager?.getSessionName?.();
2025
+ if (direct?.trim()) return direct.trim();
2026
+
2027
+ const entries = ctx?.sessionManager?.getEntries?.() ?? ctx?.sessionManager?.getBranch?.() ?? [];
2028
+ for (let i = entries.length - 1; i >= 0; i--) {
2029
+ const entry = entries[i];
2030
+ if (entry?.type === "session_info") {
2031
+ return entry.name?.trim() || undefined;
2032
+ }
2033
+ }
2034
+
2035
+ return undefined;
2036
+ }
2037
+
2038
+ function buildSegmentContext(ctx: any, theme: Theme): SegmentContext {
2039
+ const presetDef = getPreset(config.preset);
2040
+ const colors: ColorScheme = presetDef.colors ?? getDefaultColors();
2041
+
2042
+ // Build usage stats and get thinking level from session
2043
+ let input = 0, output = 0, cacheRead = 0, cacheWrite = 0, cost = 0;
2044
+ let lastAssistant: AssistantMessage | undefined;
2045
+ let thinkingLevelFromSession: string | null = null;
2046
+
2047
+ const sessionEvents = ctx.sessionManager?.getBranch?.() ?? [];
2048
+ for (const e of sessionEvents) {
2049
+ if (!isRecord(e)) {
2050
+ continue;
2051
+ }
2052
+
2053
+ // Check for thinking level change entries
2054
+ if (e.type === "thinking_level_change" && typeof e.thinkingLevel === "string") {
2055
+ thinkingLevelFromSession = e.thinkingLevel;
2056
+ }
2057
+
2058
+ if (e.type !== "message" || !isSessionAssistantMessage(e.message)) {
2059
+ continue;
2060
+ }
2061
+
2062
+ const m = e.message;
2063
+ if (m.stopReason === "error" || m.stopReason === "aborted") {
2064
+ continue;
2065
+ }
2066
+ input += m.usage.input;
2067
+ output += m.usage.output;
2068
+ cacheRead += m.usage.cacheRead;
2069
+ cacheWrite += m.usage.cacheWrite;
2070
+ cost += m.usage.cost.total;
2071
+ if (getUsageTokenTotal(m.usage) > 0) {
2072
+ lastAssistant = m;
2073
+ }
2074
+ }
2075
+
2076
+ // Calculate context percentage.
2077
+ const latestUsage = isStreaming ? liveAssistantUsage ?? lastAssistant?.usage : lastAssistant?.usage;
2078
+ const coreContextUsage = isStreaming && liveAssistantUsage ? null : readCoreContextUsage(ctx);
2079
+ const contextTokens = coreContextUsage?.contextTokens ?? (latestUsage ? getUsageTokenTotal(latestUsage) : 0);
2080
+ const contextWindow = coreContextUsage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
2081
+ const contextPercent = coreContextUsage?.contextPercent ?? (contextWindow > 0 ? (contextTokens / contextWindow) * 100 : 0);
2082
+
2083
+ // Get git status (cached)
2084
+ const gitBranch = footerDataRef?.getGitBranch() ?? null;
2085
+ const gitStatus = getGitStatus(gitBranch);
2086
+ const extensionStatuses = footerDataRef?.getExtensionStatuses() ?? new Map();
2087
+ const customItemsById = new Map(config.customItems.map((item) => [item.id, item]));
2088
+ const hiddenExtensionStatusKeys = collectHiddenExtensionStatusKeys(config.customItems);
2089
+
2090
+ // Check if using OAuth subscription
2091
+ const usingSubscription = ctx.model
2092
+ ? ctx.modelRegistry?.isUsingOAuth?.(ctx.model) ?? false
2093
+ : false;
2094
+
2095
+ const thinkingLevel = currentThinkingLevel ?? thinkingLevelFromSession ?? getThinkingLevelFn?.() ?? "off";
2096
+
2097
+ return {
2098
+ model: ctx.model,
2099
+ thinkingLevel,
2100
+ sessionId: ctx.sessionManager?.getSessionId?.(),
2101
+ sessionName: getCurrentSessionName(ctx),
2102
+ cwd: ctx.cwd,
2103
+ usageStats: { input, output, cacheRead, cacheWrite, cost },
2104
+ contextPercent,
2105
+ contextWindow,
2106
+ autoCompactEnabled: ctx.settingsManager?.getCompactionSettings?.()?.enabled ?? true,
2107
+ customCompactionEnabled: customCompactionEnabled || extensionStatuses.has(CUSTOM_COMPACTION_STATUS_KEY),
2108
+ usingSubscription,
2109
+ sessionStartTime,
2110
+ shellModeActive: bashModeActive,
2111
+ shellRunning: shellSession?.state.running ?? false,
2112
+ shellName: shellSession?.state.shellName ?? null,
2113
+ shellCwd: shellSession?.state.cwd ?? null,
2114
+ git: gitStatus,
2115
+ extensionStatuses,
2116
+ hiddenExtensionStatusKeys,
2117
+ customItemsById,
2118
+ options: presetDef.segmentOptions ?? {},
2119
+ theme,
2120
+ colors,
2121
+ };
2122
+ }
2123
+
2124
+ /**
2125
+ * Get cached responsive layout or compute fresh one.
2126
+ * The segment context scans session state, so keep it stable across render bursts.
2127
+ */
2128
+ function getResponsiveLayout(width: number, theme: Theme): { topContent: string; secondaryContent: string } {
2129
+ const now = Date.now();
2130
+ const cacheTtl = isStreaming ? STREAMING_LAYOUT_CACHE_TTL_MS : LAYOUT_CACHE_TTL_MS;
2131
+
2132
+ const currentSessionName = getCurrentSessionName(currentCtx);
2133
+ if (lastLayoutResult && lastLayoutWidth === width && currentSessionName === lastLayoutSessionName) {
2134
+ const msSinceInput = now - lastEditorInputAt;
2135
+ const typingRecently = msSinceInput < EDITOR_STATUS_DEFER_MS;
2136
+
2137
+ if (!forceNextLayoutRecompute && typingRecently && (layoutDirty || now - lastLayoutTimestamp >= cacheTtl)) {
2138
+ return lastLayoutResult;
2139
+ }
2140
+
2141
+ if (!layoutDirty && now - lastLayoutTimestamp < cacheTtl) {
2142
+ return lastLayoutResult;
2143
+ }
2144
+ }
2145
+
2146
+ const presetDef = getPreset(config.preset);
2147
+ const segmentCtx = buildSegmentContext(currentCtx, theme);
2148
+
2149
+ lastLayoutWidth = width;
2150
+ lastLayoutResult = computeResponsiveLayout(segmentCtx, presetDef, width);
2151
+ lastLayoutSessionName = currentSessionName;
2152
+ lastLayoutTimestamp = now;
2153
+ layoutDirty = false;
2154
+ forceNextLayoutRecompute = false;
2155
+
2156
+ return lastLayoutResult;
2157
+ }
2158
+
2159
+ function renderPowerlineStatusLines(width: number): string[] {
2160
+ if (!currentCtx || !footerDataRef) return [];
2161
+
2162
+ const statuses = footerDataRef.getExtensionStatuses();
2163
+ if (!statuses || statuses.size === 0) return [];
2164
+ const hiddenExtensionStatusKeys = collectHiddenExtensionStatusKeys(config.customItems);
2165
+
2166
+ const notifications: string[] = [];
2167
+ for (const value of getNotificationExtensionStatuses(statuses, hiddenExtensionStatusKeys)) {
2168
+ const lineContent = ` ${value}`;
2169
+ if (visibleWidth(lineContent) <= width) {
2170
+ notifications.push(lineContent);
2171
+ }
2172
+ }
2173
+
2174
+ return notifications;
2175
+ }
2176
+
2177
+ function renderPowerlineTopLines(width: number, theme: Theme): string[] {
2178
+ if (!currentCtx) return [];
2179
+
2180
+ const layout = getResponsiveLayout(width, theme);
2181
+ return layout.topContent ? [layout.topContent] : [];
2182
+ }
2183
+
2184
+ function renderPowerlineSecondaryLines(width: number, theme: Theme): string[] {
2185
+ if (!currentCtx) return [];
2186
+
2187
+ const layout = getResponsiveLayout(width, theme);
2188
+ return layout.secondaryContent ? [layout.secondaryContent] : [];
2189
+ }
2190
+
2191
+ function renderBashTranscriptLines(width: number, theme: Theme): string[] {
2192
+ if (!bashModeActive) return [];
2193
+
2194
+ const snapshot = bashTranscript.getSnapshot();
2195
+ if (snapshot.commands.length === 0) return [];
2196
+
2197
+ const lines: string[] = [];
2198
+ if (snapshot.truncatedCommands > 0) {
2199
+ lines.push(` ${theme.fg("dim", `… ${snapshot.truncatedCommands} earlier command${snapshot.truncatedCommands === 1 ? "" : "s"} truncated`)}`);
2200
+ }
2201
+
2202
+ const recentCommands = snapshot.commands.slice(-4);
2203
+ for (const command of recentCommands) {
2204
+ const promptGlyph = (shellSession?.state.shellName ?? "shell") === "fish" ? ">" : "$";
2205
+ const status = command.exitCode === null
2206
+ ? theme.fg("accent", "running")
2207
+ : command.exitCode === 0
2208
+ ? theme.fg("success", "ok")
2209
+ : theme.fg("error", `exit ${command.exitCode}`);
2210
+ const commandLine = truncateToWidth(command.command.replace(/\s+/g, " ").trim(), Math.max(8, width - 8), "…");
2211
+ lines.push(` ${theme.fg("accent", promptGlyph)} ${commandLine} ${theme.fg("dim", "(")}${status}${theme.fg("dim", ")")}`);
2212
+
2213
+ const outputTail = command.output.slice(-6);
2214
+ for (const outputLine of outputTail) {
2215
+ lines.push(` ${truncateToWidth(outputLine, Math.max(1, width - 3), "…")}`);
2216
+ }
2217
+ }
2218
+
2219
+ return lines.slice(-16);
2220
+ }
2221
+
2222
+ function renderLastPromptLines(width: number): string[] {
2223
+ if (bashModeActive || !showLastPrompt || !lastUserPrompt) return [];
2224
+
2225
+ const prefix = ` ${getFgAnsiCode("sep")}↳${ansi.reset} `;
2226
+ const availableWidth = width - visibleWidth(prefix);
2227
+ if (availableWidth < 10) return [];
2228
+
2229
+ let promptText = lastUserPrompt.replace(/\s+/g, " ").trim();
2230
+ if (!promptText) return [];
2231
+
2232
+ promptText = truncateToWidth(promptText, availableWidth, "…");
2233
+
2234
+ const styledPrompt = `${getFgAnsiCode("sep")}${promptText}${ansi.reset}`;
2235
+ const line = `${prefix}${styledPrompt}`;
2236
+ return [truncateToWidth(line, width, "…")];
2237
+ }
2238
+
2239
+ function teardownFixedEditorCompositor(options?: { resetExtendedKeyboardModes?: boolean }) {
2240
+ const hadCompositor = fixedEditorCompositor !== null;
2241
+ fixedEditorCompositor?.dispose(options);
2242
+ if (!hadCompositor && options?.resetExtendedKeyboardModes) {
2243
+ try {
2244
+ process.stdout.write(emergencyTerminalModeReset());
2245
+ } catch {
2246
+ // Shutdown cleanup cannot surface useful terminal write failures.
2247
+ }
2248
+ }
2249
+ fixedEditorCompositor = null;
2250
+ fixedStatusContainer = null;
2251
+ fixedEditorContainer = null;
2252
+ fixedWidgetContainerAbove = null;
2253
+ fixedWidgetContainerBelow = null;
2254
+ }
2255
+
2256
+ function findContainerWithChild(tui: any, child: any): { container: any; index: number } | null {
2257
+ const children = Array.isArray(tui?.children) ? tui.children : [];
2258
+ const index = children.findIndex((candidate: any) => Array.isArray(candidate?.children) && candidate.children.includes(child));
2259
+ if (index === -1) return null;
2260
+
2261
+ return { container: children[index], index };
2262
+ }
2263
+
2264
+ function installFixedEditorCompositor(ctx: any, tui: any) {
2265
+ teardownFixedEditorCompositor();
2266
+
2267
+ if (!ctx.hasUI || !config.fixedEditor) return;
2268
+ if (!tui?.terminal || typeof tui.terminal.write !== "function") {
2269
+ throw new Error("[powerline-footer] Fixed editor compositor could not find tui.terminal.write()");
2270
+ }
2271
+ if (!currentEditor) {
2272
+ throw new Error("[powerline-footer] Fixed editor compositor expected the custom editor to be installed first");
2273
+ }
2274
+
2275
+ const editorContainerMatch = findContainerWithChild(tui, currentEditor);
2276
+ if (!editorContainerMatch) {
2277
+ throw new Error("[powerline-footer] Fixed editor compositor could not find the editor container in TUI children");
2278
+ }
2279
+
2280
+ const tuiChildren = Array.isArray(tui.children) ? tui.children : [];
2281
+ fixedEditorContainer = editorContainerMatch.container;
2282
+ const statusContainerCandidate = tuiChildren[editorContainerMatch.index - 2] ?? null;
2283
+ fixedStatusContainer = statusContainerCandidate && typeof statusContainerCandidate.render === "function"
2284
+ ? statusContainerCandidate
2285
+ : null;
2286
+ fixedWidgetContainerAbove = tuiChildren[editorContainerMatch.index - 1] ?? null;
2287
+ fixedWidgetContainerBelow = tuiChildren[editorContainerMatch.index + 1] ?? null;
2288
+
2289
+ let compositor: TerminalSplitCompositor;
2290
+ compositor = new TerminalSplitCompositor({
2291
+ tui,
2292
+ terminal: tui.terminal,
2293
+ mouseScroll: config.mouseScroll,
2294
+ keyboardScrollShortcuts: {
2295
+ up: resolvedShortcuts.scrollChatUp,
2296
+ down: resolvedShortcuts.scrollChatDown,
2297
+ },
2298
+ onCopySelection: (text) => copyTextToClipboard(ctx, text),
2299
+ getShowHardwareCursor: () => typeof tui.getShowHardwareCursor === "function" && tui.getShowHardwareCursor(),
2300
+ renderCluster: (width, terminalRows) => {
2301
+ const theme = currentCtx?.ui?.theme ?? ctx.ui.theme;
2302
+ const statusContainerLines = fixedStatusContainer
2303
+ ? compositor.renderHidden(fixedStatusContainer, width).filter((line) => visibleWidth(line) > 0)
2304
+ : [];
2305
+ const aboveWidgetLines = fixedWidgetContainerAbove ? compositor.renderHidden(fixedWidgetContainerAbove, width) : [];
2306
+ const belowWidgetLines = fixedWidgetContainerBelow ? compositor.renderHidden(fixedWidgetContainerBelow, width) : [];
2307
+ return renderFixedEditorCluster({
2308
+ width,
2309
+ terminalRows,
2310
+ statusLines: [...aboveWidgetLines, ...renderPowerlineStatusLines(width), ...statusContainerLines],
2311
+ topLines: renderPowerlineTopLines(width, theme),
2312
+ editorLines: fixedEditorContainer ? compositor.renderHidden(fixedEditorContainer, width) : [],
2313
+ secondaryLines: [...renderPowerlineSecondaryLines(width, theme), ...belowWidgetLines],
2314
+ transcriptLines: renderBashTranscriptLines(width, theme),
2315
+ lastPromptLines: renderLastPromptLines(width),
2316
+ });
2317
+ },
2318
+ });
2319
+
2320
+ fixedEditorCompositor = compositor;
2321
+ if (fixedStatusContainer?.render) compositor.hideRenderable(fixedStatusContainer);
2322
+ if (fixedWidgetContainerAbove?.render) compositor.hideRenderable(fixedWidgetContainerAbove);
2323
+ compositor.hideRenderable(fixedEditorContainer);
2324
+ if (fixedWidgetContainerBelow?.render) compositor.hideRenderable(fixedWidgetContainerBelow);
2325
+ compositor.install();
2326
+ tui.requestRender(true);
2327
+ }
2328
+
2329
+ function isChatMessageComponentForRole(component: unknown, role: ChatJumpRole): boolean {
2330
+ const componentName = typeof component === "object" && component !== null ? component.constructor?.name : undefined;
2331
+ if (role === "assistant") {
2332
+ return componentName === "AssistantMessageComponent";
2333
+ }
2334
+
2335
+ return componentName === "UserMessageComponent" || componentName === "SkillInvocationMessageComponent";
2336
+ }
2337
+
2338
+ function renderLineCount(component: unknown, width: number): number {
2339
+ if (typeof component !== "object" || component === null) return 0;
2340
+
2341
+ const render = Reflect.get(component, "render");
2342
+ if (typeof render !== "function") return 0;
2343
+
2344
+ const lines = render.call(component, width);
2345
+ return Array.isArray(lines) ? lines.length : 0;
2346
+ }
2347
+
2348
+ function collectMessageStartLines(component: unknown, width: number, role: ChatJumpRole, offset: number): {
2349
+ targets: number[];
2350
+ lineCount: number;
2351
+ } {
2352
+ const lineCount = renderLineCount(component, width);
2353
+ if (isChatMessageComponentForRole(component, role)) {
2354
+ return { targets: [offset], lineCount };
2355
+ }
2356
+
2357
+ const children = typeof component === "object" && component !== null ? Reflect.get(component, "children") : null;
2358
+ if (!Array.isArray(children) || children.length === 0) {
2359
+ return { targets: [], lineCount };
2360
+ }
2361
+
2362
+ const targets: number[] = [];
2363
+ let childOffset = offset;
2364
+ let childrenLineCount = 0;
2365
+ for (const child of children) {
2366
+ const result = collectMessageStartLines(child, width, role, childOffset);
2367
+ targets.push(...result.targets);
2368
+ childOffset += result.lineCount;
2369
+ childrenLineCount += result.lineCount;
2370
+ }
2371
+
2372
+ return { targets, lineCount: Math.max(lineCount, childrenLineCount) };
2373
+ }
2374
+
2375
+ function collectChatMessageStartLines(role: ChatJumpRole): number[] {
2376
+ const children = Array.isArray(tuiRef?.children) ? tuiRef.children : [];
2377
+ const width = Math.max(1, tuiRef?.terminal?.columns ?? 80);
2378
+ const targets: number[] = [];
2379
+ let offset = 0;
2380
+
2381
+ for (const child of children) {
2382
+ const result = collectMessageStartLines(child, width, role, offset);
2383
+ targets.push(...result.targets);
2384
+ offset += result.lineCount;
2385
+ }
2386
+
2387
+ return [...new Set(targets)].sort((a, b) => a - b);
2388
+ }
2389
+
2390
+ function jumpToChatMessage(ctx: any, role: ChatJumpRole, direction: ChatJumpDirection): void {
2391
+ if (!fixedEditorCompositor) {
2392
+ ctx.ui.notify("Chat message jumps require /powerline fixed-editor on", "warning");
2393
+ return;
2394
+ }
2395
+
2396
+ const targets = collectChatMessageStartLines(role);
2397
+ const label = role === "assistant" ? "LLM" : "user";
2398
+ if (targets.length === 0) {
2399
+ ctx.ui.notify(`No ${label} messages found`, "info");
2400
+ return;
2401
+ }
2402
+
2403
+ const jumped = direction === "previous"
2404
+ ? fixedEditorCompositor.jumpToPreviousRootTarget(targets)
2405
+ : fixedEditorCompositor.jumpToNextRootTarget(targets);
2406
+ if (!jumped) {
2407
+ ctx.ui.notify(`No ${direction} ${label} message`, "info");
2408
+ }
2409
+ }
2410
+
2411
+ function jumpChatToBottom(ctx: any): void {
2412
+ if (!fixedEditorCompositor) {
2413
+ ctx.ui.notify("Chat bottom jump requires /powerline fixed-editor on", "warning");
2414
+ return;
2415
+ }
2416
+
2417
+ fixedEditorCompositor.jumpToRootBottom();
2418
+ }
2419
+
2420
+ function followSubmittedEditorToBottom(): void {
2421
+ fixedEditorCompositor?.jumpToRootBottom();
2422
+ }
2423
+
2424
+ function installPowerlineWidgets(ctx: any) {
2425
+ ctx.ui.setWidget("powerline-status", () => ({
2426
+ dispose() {},
2427
+ invalidate() {
2428
+ requestStatusRender();
2429
+ },
2430
+ render(width: number): string[] {
2431
+ return renderPowerlineStatusLines(width);
2432
+ },
2433
+ }), { placement: "aboveEditor" });
2434
+
2435
+ ctx.ui.setWidget("powerline-top", (_tui: any, theme: Theme) => ({
2436
+ dispose() {},
2437
+ invalidate() {
2438
+ resetLayoutCache();
2439
+ },
2440
+ render(width: number): string[] {
2441
+ return renderPowerlineTopLines(width, theme);
2442
+ },
2443
+ }), { placement: "aboveEditor" });
2444
+
2445
+ ctx.ui.setWidget("powerline-secondary", (_tui: any, theme: Theme) => ({
2446
+ dispose() {},
2447
+ invalidate() {
2448
+ resetLayoutCache();
2449
+ },
2450
+ render(width: number): string[] {
2451
+ return renderPowerlineSecondaryLines(width, theme);
2452
+ },
2453
+ }), { placement: "belowEditor" });
2454
+
2455
+ ctx.ui.setWidget("powerline-bash-transcript", (_tui: any, theme: Theme) => ({
2456
+ dispose() {},
2457
+ invalidate() {},
2458
+ render(width: number): string[] {
2459
+ return renderBashTranscriptLines(width, theme);
2460
+ },
2461
+ }), { placement: "belowEditor" });
2462
+
2463
+ ctx.ui.setWidget("powerline-last-prompt", () => ({
2464
+ dispose() {},
2465
+ invalidate() {},
2466
+ render(width: number): string[] {
2467
+ return renderLastPromptLines(width);
2468
+ },
2469
+ }), { placement: "belowEditor" });
2470
+ }
2471
+
2472
+ function setupCustomEditor(ctx: any) {
2473
+ snapshotPromptHistory(currentEditor);
2474
+ if (!enabled) {
2475
+ return;
2476
+ }
2477
+
2478
+ stashShortcutInputUnsubscribe?.();
2479
+ stashShortcutInputUnsubscribe = typeof ctx.ui.onTerminalInput === "function"
2480
+ ? ctx.ui.onTerminalInput((data: string) => {
2481
+ if (!enabled || !ctx.hasUI || tuiRef?.hasOverlay?.()) {
2482
+ return undefined;
2483
+ }
2484
+ if (isStashShortcutInput(data)) {
2485
+ stashOrRestoreEditorText(ctx);
2486
+ scheduleDismissWelcome(ctx);
2487
+ tuiRef?.requestRender();
2488
+ return { consume: true };
2489
+ }
2490
+
2491
+ const powerlineShortcutAction = getPowerlineShortcutAction(data);
2492
+ if (!powerlineShortcutAction) {
2493
+ return undefined;
2494
+ }
2495
+
2496
+ runPowerlineShortcut(ctx, powerlineShortcutAction);
2497
+ scheduleDismissWelcome(ctx);
2498
+ tuiRef?.requestRender();
2499
+ return { consume: true };
2500
+ })
2501
+ : null;
2502
+
2503
+ teardownFixedEditorCompositor();
2504
+ ctx.ui.setWidget("powerline-top", undefined);
2505
+ ctx.ui.setWidget("powerline-secondary", undefined);
2506
+ ctx.ui.setWidget("powerline-bash-transcript", undefined);
2507
+ ctx.ui.setWidget("powerline-status", undefined);
2508
+ ctx.ui.setWidget("powerline-last-prompt", undefined);
2509
+
2510
+ let autocompleteFixed = false;
2511
+
2512
+ const editorFactory = (tui: any, editorTheme: any, keybindings: any) => {
2513
+ const editor = new BashModeEditor(tui, editorTheme, keybindings, {
2514
+ keybindings,
2515
+ isBashModeActive: () => bashModeActive,
2516
+ isShellRunning: () => shellSession?.state.running ?? false,
2517
+ onExitBashMode: () => {
2518
+ void setBashModeActive(false, ctx);
2519
+ },
2520
+ onSubmitCommand: (command) => void runShellCommand(command, ctx),
2521
+ onEditorSubmit: () => followSubmittedEditorToBottom(),
2522
+ editorBoundaryShortcuts: {
2523
+ start: resolvedShortcuts.editorStart,
2524
+ end: resolvedShortcuts.editorEnd,
2525
+ },
2526
+ onInterrupt: () => {
2527
+ shellSession?.interrupt();
2528
+ ctx.ui.notify("Sent interrupt to shell", "info");
2529
+ },
2530
+ onNotify: (message, level = "info") => ctx.ui.notify(message, level),
2531
+ getHistoryEntries: (prefix) => getShellHistoryEntries(prefix),
2532
+ resolveGhostSuggestion: async (text, signal) => {
2533
+ const oneOffBash = getOneOffBashCommandContext(text);
2534
+ if (oneOffBash) {
2535
+ const ghost = await bashCompletionEngine.getGhostSuggestion(
2536
+ oneOffBash.command,
2537
+ getShellCwd(),
2538
+ getShellPath(),
2539
+ signal,
2540
+ );
2541
+ return ghost ? { ...ghost, value: `${oneOffBash.prefix}${ghost.value}` } : null;
2542
+ }
2543
+
2544
+ return bashCompletionEngine.getGhostSuggestion(text, getShellCwd(), getShellPath(), signal);
2545
+ },
2546
+ });
2547
+
2548
+ const getInstalledAutocompleteProvider = (): AutocompleteProvider | undefined => {
2549
+ const candidate = Reflect.get(editor, "autocompleteProvider");
2550
+ if (!candidate || typeof candidate !== "object") {
2551
+ return undefined;
2552
+ }
2553
+ if (typeof Reflect.get(candidate, "getSuggestions") !== "function") {
2554
+ return undefined;
2555
+ }
2556
+ if (typeof Reflect.get(candidate, "applyCompletion") !== "function") {
2557
+ return undefined;
2558
+ }
2559
+ return candidate;
2560
+ };
2561
+
2562
+ const attachAutocompleteProvider = (): boolean => {
2563
+ if (editor.hasWrappedProvider()) return true;
2564
+ const defaultProvider = getInstalledAutocompleteProvider();
2565
+ if (!defaultProvider) return false;
2566
+
2567
+ const bashProvider = new BashAutocompleteProvider();
2568
+ const oneOffBashProvider = new OneOffBashAutocompleteProvider();
2569
+ editor.installAutocompleteProvider(
2570
+ new ModeAwareAutocompleteProvider(defaultProvider, bashProvider, oneOffBashProvider, () => bashModeActive),
2571
+ );
2572
+ return true;
2573
+ };
2574
+
2575
+ let inheritedOnSubmit: unknown;
2576
+ Object.defineProperty(editor, "onSubmit", {
2577
+ configurable: true,
2578
+ get: () => inheritedOnSubmit,
2579
+ set(handler: unknown) {
2580
+ inheritedOnSubmit = typeof handler === "function"
2581
+ ? (text: string) => {
2582
+ followSubmittedEditorToBottom();
2583
+ handler(text);
2584
+ }
2585
+ : handler;
2586
+ },
2587
+ });
2588
+
2589
+ currentEditor = editor;
2590
+ trackPromptHistory(editor);
2591
+ restorePromptHistory(editor);
2592
+ attachAutocompleteProvider();
2593
+
2594
+ const originalHandleInput = editor.handleInput.bind(editor);
2595
+ editor.handleInput = (data: string) => {
2596
+ lastEditorInputAt = Date.now();
2597
+
2598
+ if (isStashShortcutInput(data)) {
2599
+ stashOrRestoreEditorText(ctx);
2600
+ scheduleDismissWelcome(ctx);
2601
+ return;
2602
+ }
2603
+
2604
+ const powerlineShortcutAction = getPowerlineShortcutAction(data);
2605
+ if (powerlineShortcutAction) {
2606
+ runPowerlineShortcut(ctx, powerlineShortcutAction);
2607
+ scheduleDismissWelcome(ctx);
2608
+ return;
2609
+ }
2610
+
2611
+ if (!autocompleteFixed && !getInstalledAutocompleteProvider()) {
2612
+ autocompleteFixed = true;
2613
+ snapshotPromptHistory(editor);
2614
+ ctx.ui.setEditorComponent(editorFactory);
2615
+ if (config.fixedEditor) {
2616
+ installFixedEditorCompositor(ctx, tui);
2617
+ }
2618
+ currentEditor?.handleInput(data);
2619
+ return;
2620
+ }
2621
+
2622
+ attachAutocompleteProvider();
2623
+ const followUpText = keybindings.matches(data, "app.message.followUp") ? getCurrentEditorText(ctx, editor) : "";
2624
+ scheduleDismissWelcome(ctx);
2625
+ originalHandleInput(data);
2626
+ if (hasNonWhitespaceText(followUpText) && !hasNonWhitespaceText(getCurrentEditorText(ctx, editor))) {
2627
+ followSubmittedEditorToBottom();
2628
+ }
2629
+ };
2630
+
2631
+ const originalRender = editor.render.bind(editor);
2632
+ editor.render = (width: number): string[] => {
2633
+ if (width < 10) {
2634
+ return originalRender(width);
2635
+ }
2636
+
2637
+ const bc = (s: string) => `${getFgAnsiCode("sep")}${s}${ansi.reset}`;
2638
+ const promptGlyph = bashModeActive ? "$" : config.fixedEditorPromptGlyph;
2639
+ const prompt = promptGlyph ? `${ansi.getFgAnsi(200, 200, 200)}${promptGlyph}${ansi.reset}` : "";
2640
+ const promptPrefix = prompt ? ` ${prompt} ` : " ";
2641
+ const contPrefix = " ";
2642
+ const contentWidth = Math.max(1, width - 1);
2643
+ const lines = originalRender(contentWidth);
2644
+
2645
+ if (lines.length === 0) return lines;
2646
+
2647
+ let bottomBorderIndex = lines.length - 1;
2648
+ for (let i = lines.length - 1; i >= 1; i--) {
2649
+ const stripped = lines[i]?.replace(/\x1b\[[0-9;]*m/g, "") || "";
2650
+ if (stripped.length > 0 && /^─{3,}/.test(stripped)) {
2651
+ bottomBorderIndex = i;
2652
+ break;
2653
+ }
2654
+ }
2655
+
2656
+ const result: string[] = [];
2657
+ result.push(" " + bc("─".repeat(width - 2)));
2658
+
2659
+ for (let i = 1; i < bottomBorderIndex; i++) {
2660
+ const prefix = i === 1 ? promptPrefix : contPrefix;
2661
+ result.push(`${prefix}${lines[i] || ""}`);
2662
+ }
2663
+
2664
+ if (bottomBorderIndex === 1) {
2665
+ result.push(`${promptPrefix}${" ".repeat(contentWidth)}`);
2666
+ }
2667
+
2668
+ result.push(" " + bc("─".repeat(width - 2)));
2669
+
2670
+ for (let i = bottomBorderIndex + 1; i < lines.length; i++) {
2671
+ result.push(lines[i] || "");
2672
+ }
2673
+
2674
+ return result;
2675
+ };
2676
+
2677
+ return editor;
2678
+ };
2679
+
2680
+ ctx.ui.setEditorComponent(editorFactory);
2681
+
2682
+ ctx.ui.setFooter((tui: any, _theme: Theme, footerData: ReadonlyFooterDataProvider) => {
2683
+ footerDataRef = footerData;
2684
+ tuiRef = tui;
2685
+ installFooterStatusRepaintHook(footerData);
2686
+ const unsub = footerData.onBranchChange(() => requestStatusRender());
2687
+
2688
+ return {
2689
+ dispose() {
2690
+ unsub();
2691
+ restoreFooterStatusRepaintHook?.();
2692
+ restoreFooterStatusRepaintHook = null;
2693
+ },
2694
+ invalidate() {
2695
+ requestStatusRender();
2696
+ },
2697
+ render(): string[] {
2698
+ return [];
2699
+ },
2700
+ };
2701
+ });
2702
+
2703
+ if (config.fixedEditor) {
2704
+ if (tuiRef) {
2705
+ installFixedEditorCompositor(ctx, tuiRef);
2706
+ }
2707
+ } else {
2708
+ installPowerlineWidgets(ctx);
2709
+ }
2710
+ }
2711
+
2712
+ function setupWelcomeHeader(ctx: any) {
2713
+ const modelName = ctx.model?.name || ctx.model?.id || "No model";
2714
+ const providerName = ctx.model?.provider || "Unknown";
2715
+ const loadedCounts = discoverLoadedCounts();
2716
+ const recentSessions = getRecentSessions(3);
2717
+
2718
+ const header = new WelcomeHeader(modelName, providerName, recentSessions, loadedCounts);
2719
+ welcomeHeaderActive = true;
2720
+
2721
+ ctx.ui.setHeader(() => {
2722
+ return {
2723
+ render(width: number): string[] {
2724
+ return header.render(width);
2725
+ },
2726
+ invalidate() {
2727
+ header.invalidate();
2728
+ },
2729
+ };
2730
+ });
2731
+ }
2732
+
2733
+ function setupWelcomeOverlay(ctx: any) {
2734
+ const modelName = ctx.model?.name || ctx.model?.id || "No model";
2735
+ const providerName = ctx.model?.provider || "Unknown";
2736
+ const loadedCounts = discoverLoadedCounts();
2737
+ const recentSessions = getRecentSessions(3);
2738
+
2739
+ const overlaySessionGeneration = sessionGeneration;
2740
+
2741
+ // Small delay to let pi-mono finish initialization
2742
+ setTimeout(() => {
2743
+ if (!enabled || welcomeOverlayShouldDismiss || isStreaming || overlaySessionGeneration !== sessionGeneration) {
2744
+ welcomeOverlayShouldDismiss = false;
2745
+ return;
2746
+ }
2747
+
2748
+ const sessionEvents = ctx.sessionManager?.getBranch?.() ?? [];
2749
+ const hasActivity = sessionEvents.some((entry: unknown) => {
2750
+ if (!isRecord(entry)) return false;
2751
+ if (entry.type === "tool_call" || entry.type === "tool_result") return true;
2752
+ return entry.type === "message" && isRecord(entry.message) && entry.message.role === "assistant";
2753
+ });
2754
+ if (hasActivity) {
2755
+ return;
2756
+ }
2757
+
2758
+ ctx.ui.custom(
2759
+ (tui: any, _theme: any, _keybindings: any, done: (result: void) => void) => {
2760
+ const welcome = new WelcomeComponent(
2761
+ modelName,
2762
+ providerName,
2763
+ recentSessions,
2764
+ loadedCounts,
2765
+ );
2766
+
2767
+ let countdown = 30;
2768
+ let dismissed = false;
2769
+ let interval: ReturnType<typeof setInterval> | null = null;
2770
+
2771
+ const dismiss = () => {
2772
+ if (dismissed) return;
2773
+ dismissed = true;
2774
+ if (interval) clearInterval(interval);
2775
+ dismissWelcomeOverlay = null;
2776
+ done();
2777
+ };
2778
+
2779
+ interval = setInterval(() => {
2780
+ if (dismissed) return;
2781
+ countdown--;
2782
+ welcome.setCountdown(countdown);
2783
+ tui.requestRender();
2784
+ if (countdown <= 0) dismiss();
2785
+ }, 1000);
2786
+
2787
+ dismissWelcomeOverlay = dismiss;
2788
+
2789
+ if (welcomeOverlayShouldDismiss) {
2790
+ welcomeOverlayShouldDismiss = false;
2791
+ dismiss();
2792
+ }
2793
+
2794
+ return {
2795
+ focused: false,
2796
+ invalidate: () => welcome.invalidate(),
2797
+ render: (width: number) => welcome.render(width),
2798
+ handleInput: () => dismiss(),
2799
+ dispose: () => {
2800
+ dismissed = true;
2801
+ if (interval) clearInterval(interval);
2802
+ },
2803
+ };
2804
+ },
2805
+ {
2806
+ overlay: true,
2807
+ overlayOptions: () => ({
2808
+ verticalAlign: "center",
2809
+ horizontalAlign: "center",
2810
+ }),
2811
+ },
2812
+ ).catch((error) => {
2813
+ console.debug("[powerline-footer] Welcome overlay failed:", error);
2814
+ });
2815
+ }, 100);
2816
+ }
2817
+ }