@bastani/atomic 0.8.4-0 → 0.8.5-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 (245) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +24 -23
  3. package/dist/builtin/intercom/README.md +5 -5
  4. package/dist/builtin/intercom/index.ts +1 -1
  5. package/dist/builtin/intercom/package.json +1 -1
  6. package/dist/builtin/intercom/ui/compose.ts +19 -1
  7. package/dist/builtin/intercom/ui/session-list.ts +19 -1
  8. package/dist/builtin/mcp/README.md +3 -3
  9. package/dist/builtin/mcp/commands.ts +1 -1
  10. package/dist/builtin/mcp/host-html-template.ts +1 -1
  11. package/dist/builtin/mcp/mcp-panel.ts +14 -14
  12. package/dist/builtin/mcp/mcp-setup-panel.ts +4 -4
  13. package/dist/builtin/mcp/package.json +1 -1
  14. package/dist/builtin/mcp/tool-result-renderer.ts +1 -1
  15. package/dist/builtin/subagents/README.md +3 -3
  16. package/dist/builtin/subagents/package.json +1 -1
  17. package/dist/builtin/subagents/src/tui/render.ts +1844 -1062
  18. package/dist/builtin/web-access/README.md +1 -1
  19. package/dist/builtin/web-access/curator-page.ts +2 -2
  20. package/dist/builtin/web-access/index.ts +1 -1
  21. package/dist/builtin/web-access/package.json +1 -1
  22. package/dist/builtin/workflows/README.md +34 -7
  23. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +23 -4
  24. package/dist/builtin/workflows/builtin/ralph.ts +1 -1
  25. package/dist/builtin/workflows/package.json +1 -1
  26. package/dist/builtin/workflows/skills/workflow/SKILL.md +75 -16
  27. package/dist/builtin/workflows/skills/workflow/references/running-workflows.md +34 -11
  28. package/dist/builtin/workflows/skills/workflow/references/sdk-authoring.md +111 -20
  29. package/dist/builtin/workflows/src/extension/discovery.ts +32 -4
  30. package/dist/builtin/workflows/src/extension/index.ts +347 -63
  31. package/dist/builtin/workflows/src/extension/render-call.ts +3 -1
  32. package/dist/builtin/workflows/src/extension/render-result.ts +7 -0
  33. package/dist/builtin/workflows/src/extension/runtime.ts +4 -2
  34. package/dist/builtin/workflows/src/extension/wiring.ts +32 -8
  35. package/dist/builtin/workflows/src/extension/workflow-schema.ts +36 -14
  36. package/dist/builtin/workflows/src/runs/background/runner.ts +2 -2
  37. package/dist/builtin/workflows/src/runs/background/status.ts +89 -0
  38. package/dist/builtin/workflows/src/runs/foreground/executor.ts +338 -78
  39. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +2 -0
  40. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +55 -7
  41. package/dist/builtin/workflows/src/runs/shared/workflow-runner.ts +146 -10
  42. package/dist/builtin/workflows/src/shared/store.ts +29 -0
  43. package/dist/builtin/workflows/src/shared/types.ts +25 -4
  44. package/dist/builtin/workflows/src/tui/graph-canvas.ts +69 -2
  45. package/dist/builtin/workflows/src/tui/graph-view.ts +97 -182
  46. package/dist/builtin/workflows/src/tui/header.ts +36 -20
  47. package/dist/builtin/workflows/src/tui/inline-form-card.ts +129 -46
  48. package/dist/builtin/workflows/src/tui/inline-form-editor.ts +111 -36
  49. package/dist/builtin/workflows/src/tui/inputs-picker.ts +311 -91
  50. package/dist/builtin/workflows/src/tui/layout.ts +1 -1
  51. package/dist/builtin/workflows/src/tui/node-card.ts +66 -37
  52. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +20 -6
  53. package/dist/builtin/workflows/src/tui/prompt-card.ts +262 -85
  54. package/dist/builtin/workflows/src/tui/run-detail.ts +50 -31
  55. package/dist/builtin/workflows/src/tui/session-confirm.ts +21 -14
  56. package/dist/builtin/workflows/src/tui/session-picker.ts +35 -26
  57. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +531 -960
  58. package/dist/builtin/workflows/src/tui/status-helpers.ts +6 -0
  59. package/dist/builtin/workflows/src/tui/status-list.ts +8 -4
  60. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +7 -2
  61. package/dist/builtin/workflows/src/tui/switcher.ts +55 -25
  62. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +33 -1
  63. package/dist/builtin/workflows/src/tui/workflow-list.ts +10 -6
  64. package/dist/cli/args.d.ts.map +1 -1
  65. package/dist/cli/args.js +1 -1
  66. package/dist/cli/args.js.map +1 -1
  67. package/dist/config.d.ts.map +1 -1
  68. package/dist/config.js +20 -6
  69. package/dist/config.js.map +1 -1
  70. package/dist/core/agent-session-services.d.ts +3 -3
  71. package/dist/core/agent-session-services.d.ts.map +1 -1
  72. package/dist/core/agent-session-services.js.map +1 -1
  73. package/dist/core/agent-session.d.ts +7 -7
  74. package/dist/core/agent-session.d.ts.map +1 -1
  75. package/dist/core/agent-session.js.map +1 -1
  76. package/dist/core/compaction/branch-summarization.d.ts +2 -2
  77. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  78. package/dist/core/compaction/branch-summarization.js.map +1 -1
  79. package/dist/core/compaction/compaction.d.ts +3 -3
  80. package/dist/core/compaction/compaction.d.ts.map +1 -1
  81. package/dist/core/compaction/compaction.js.map +1 -1
  82. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  83. package/dist/core/export-html/tool-renderer.js.map +1 -1
  84. package/dist/core/extensions/loader.d.ts +3 -2
  85. package/dist/core/extensions/loader.d.ts.map +1 -1
  86. package/dist/core/extensions/loader.js +24 -12
  87. package/dist/core/extensions/loader.js.map +1 -1
  88. package/dist/core/extensions/runner.d.ts.map +1 -1
  89. package/dist/core/extensions/runner.js +6 -0
  90. package/dist/core/extensions/runner.js.map +1 -1
  91. package/dist/core/extensions/types.d.ts +28 -17
  92. package/dist/core/extensions/types.d.ts.map +1 -1
  93. package/dist/core/extensions/types.js.map +1 -1
  94. package/dist/core/package-manager.d.ts +1 -0
  95. package/dist/core/package-manager.d.ts.map +1 -1
  96. package/dist/core/package-manager.js +65 -28
  97. package/dist/core/package-manager.js.map +1 -1
  98. package/dist/core/resource-loader.d.ts.map +1 -1
  99. package/dist/core/resource-loader.js +13 -5
  100. package/dist/core/resource-loader.js.map +1 -1
  101. package/dist/core/sdk.d.ts +3 -3
  102. package/dist/core/sdk.d.ts.map +1 -1
  103. package/dist/core/sdk.js.map +1 -1
  104. package/dist/core/session-manager.d.ts.map +1 -1
  105. package/dist/core/session-manager.js +1 -1
  106. package/dist/core/session-manager.js.map +1 -1
  107. package/dist/core/settings-manager.d.ts +2 -0
  108. package/dist/core/settings-manager.d.ts.map +1 -1
  109. package/dist/core/settings-manager.js.map +1 -1
  110. package/dist/core/slash-commands.d.ts.map +1 -1
  111. package/dist/core/slash-commands.js +1 -1
  112. package/dist/core/slash-commands.js.map +1 -1
  113. package/dist/core/system-prompt.d.ts.map +1 -1
  114. package/dist/core/system-prompt.js +5 -3
  115. package/dist/core/system-prompt.js.map +1 -1
  116. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts +1 -1
  117. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts.map +1 -1
  118. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js +1 -1
  119. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js.map +1 -1
  120. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts +8 -8
  121. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts.map +1 -1
  122. package/dist/core/tools/ask-user-question/view/dialog-builder.js +6 -6
  123. package/dist/core/tools/ask-user-question/view/dialog-builder.js.map +1 -1
  124. package/dist/core/tools/bash.d.ts.map +1 -1
  125. package/dist/core/tools/bash.js +1 -1
  126. package/dist/core/tools/bash.js.map +1 -1
  127. package/dist/core/tools/find.d.ts.map +1 -1
  128. package/dist/core/tools/find.js +1 -1
  129. package/dist/core/tools/find.js.map +1 -1
  130. package/dist/core/tools/grep.d.ts.map +1 -1
  131. package/dist/core/tools/grep.js +7 -4
  132. package/dist/core/tools/grep.js.map +1 -1
  133. package/dist/core/tools/index.d.ts +3 -2
  134. package/dist/core/tools/index.d.ts.map +1 -1
  135. package/dist/core/tools/index.js.map +1 -1
  136. package/dist/core/tools/ls.d.ts.map +1 -1
  137. package/dist/core/tools/ls.js +3 -2
  138. package/dist/core/tools/ls.js.map +1 -1
  139. package/dist/core/tools/read.d.ts.map +1 -1
  140. package/dist/core/tools/read.js +2 -2
  141. package/dist/core/tools/read.js.map +1 -1
  142. package/dist/core/tools/render-utils.d.ts +2 -1
  143. package/dist/core/tools/render-utils.d.ts.map +1 -1
  144. package/dist/core/tools/render-utils.js.map +1 -1
  145. package/dist/core/tools/todos.d.ts.map +1 -1
  146. package/dist/core/tools/todos.js +1 -1
  147. package/dist/core/tools/todos.js.map +1 -1
  148. package/dist/core/tools/tool-definition-wrapper.d.ts +4 -3
  149. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  150. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  151. package/dist/core/tools/write.d.ts.map +1 -1
  152. package/dist/core/tools/write.js +1 -1
  153. package/dist/core/tools/write.js.map +1 -1
  154. package/dist/index.d.ts +2 -1
  155. package/dist/index.d.ts.map +1 -1
  156. package/dist/index.js +2 -1
  157. package/dist/index.js.map +1 -1
  158. package/dist/main.d.ts.map +1 -1
  159. package/dist/main.js +2 -2
  160. package/dist/main.js.map +1 -1
  161. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  162. package/dist/modes/interactive/components/assistant-message.js +3 -3
  163. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  164. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/bash-execution.js +3 -3
  166. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  167. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  168. package/dist/modes/interactive/components/branch-summary-message.js +1 -1
  169. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  170. package/dist/modes/interactive/components/chat-message-renderer.d.ts +2 -1
  171. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  172. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  173. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  174. package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
  175. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  176. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  177. package/dist/modes/interactive/components/config-selector.js +1 -1
  178. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  179. package/dist/modes/interactive/components/custom-editor.d.ts +3 -0
  180. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  181. package/dist/modes/interactive/components/custom-editor.js +13 -3
  182. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  183. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  184. package/dist/modes/interactive/components/footer.js +1 -1
  185. package/dist/modes/interactive/components/footer.js.map +1 -1
  186. package/dist/modes/interactive/components/index.d.ts +2 -1
  187. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  188. package/dist/modes/interactive/components/index.js +2 -1
  189. package/dist/modes/interactive/components/index.js.map +1 -1
  190. package/dist/modes/interactive/components/keybinding-hints.d.ts +1 -0
  191. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  192. package/dist/modes/interactive/components/keybinding-hints.js +47 -5
  193. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  194. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  195. package/dist/modes/interactive/components/login-dialog.js +5 -5
  196. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  197. package/dist/modes/interactive/components/model-selector.d.ts +3 -3
  198. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  199. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  200. package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
  201. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  202. package/dist/modes/interactive/components/scoped-models-selector.js +7 -7
  203. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  204. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  205. package/dist/modes/interactive/components/session-selector.js +8 -8
  206. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  207. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  208. package/dist/modes/interactive/components/settings-selector.js +3 -3
  209. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  210. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  211. package/dist/modes/interactive/components/skill-invocation-message.js +2 -2
  212. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  213. package/dist/modes/interactive/components/tool-execution.d.ts +10 -12
  214. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  215. package/dist/modes/interactive/components/tool-execution.js +3 -3
  216. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  217. package/dist/modes/interactive/components/working-status.d.ts +25 -0
  218. package/dist/modes/interactive/components/working-status.d.ts.map +1 -0
  219. package/dist/modes/interactive/components/working-status.js +28 -0
  220. package/dist/modes/interactive/components/working-status.js.map +1 -0
  221. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  222. package/dist/modes/interactive/interactive-mode.js +8 -7
  223. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  224. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  225. package/dist/modes/rpc/rpc-mode.js +8 -0
  226. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  227. package/dist/modes/rpc/rpc-types.d.ts +5 -5
  228. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  229. package/dist/modes/rpc/rpc-types.js.map +1 -1
  230. package/dist/utils/tools-manager.d.ts.map +1 -1
  231. package/dist/utils/tools-manager.js.map +1 -1
  232. package/docs/development.md +2 -2
  233. package/docs/extensions.md +7 -7
  234. package/docs/packages.md +11 -8
  235. package/docs/quickstart.md +2 -2
  236. package/docs/rpc.md +1 -1
  237. package/docs/sdk.md +14 -11
  238. package/docs/session-format.md +1 -1
  239. package/docs/sessions.md +10 -10
  240. package/docs/settings.md +1 -1
  241. package/docs/terminal-setup.md +9 -9
  242. package/docs/tmux.md +10 -10
  243. package/docs/tui.md +2 -2
  244. package/docs/usage.md +9 -9
  245. package/package.json +6 -1
@@ -1 +1 @@
1
- {"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,GAAG,EAAgB,MAAM,wBAAwB,CAAC;AAC9G,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEtF,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD;;GAEG;AACH,qBAAa,YAAa,SAAQ,MAAM;IACvC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,YAAY,CAAS;IACtB,cAAc,EAAE,GAAG,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,CAAa;IAG3D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,2EAA2E;IACpE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAEvD,YAAY,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,mBAAmB,EAIvG;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA8B9B;IAED,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,OAAO;IAQf;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAEzD;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAiD9B;CACD","sourcesContent":["import { Editor, type EditorOptions, type EditorTheme, type TUI, visibleWidth } from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.js\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\tpromptPrefix?: string;\n}\n\nconst ANSI_ESCAPE_PATTERN = /\\x1b\\[[0-?]*[ -/]*[@-~]|\\x1b\\][^\\x07]*(?:\\x07|\\x1b\\\\)|\\x1b[PX_][\\s\\S]*?\\x1b\\\\/g;\nconst BORDER_LINE_PATTERN = /^[─ ↑↓0-9more]+$/;\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate promptPrefix: string;\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tthis.promptPrefix = options?.promptPrefix ?? \"❯ \";\n\t}\n\n\trender(width: number): string[] {\n\t\tconst promptWidth = visibleWidth(this.promptPrefix);\n\t\tif (promptWidth <= 0 || width <= promptWidth + 1) {\n\t\t\treturn super.render(width);\n\t\t}\n\n\t\tconst editorWidth = Math.max(1, width - promptWidth);\n\t\tconst lines = super.render(editorWidth);\n\t\tlet borderCount = 0;\n\t\tlet inPromptBox = false;\n\t\tlet promptShown = false;\n\n\t\treturn lines.map((line) => {\n\t\t\tif (this.isEditorBorderLine(line)) {\n\t\t\t\tborderCount += 1;\n\t\t\t\tif (borderCount === 1) {\n\t\t\t\t\tinPromptBox = true;\n\t\t\t\t\tpromptShown = false;\n\t\t\t\t} else if (borderCount === 2) {\n\t\t\t\t\tinPromptBox = false;\n\t\t\t\t}\n\t\t\t\treturn this.extendBorderLine(line, width);\n\t\t\t}\n\n\t\t\tconst prefix = inPromptBox && !promptShown ? this.promptPrefix : \" \".repeat(promptWidth);\n\t\t\tif (inPromptBox) {\n\t\t\t\tpromptShown = true;\n\t\t\t}\n\t\t\treturn this.padLine(`${prefix}${line}`, width);\n\t\t});\n\t}\n\n\tprivate isEditorBorderLine(line: string): boolean {\n\t\tconst plain = line.replace(ANSI_ESCAPE_PATTERN, \"\").trim();\n\t\treturn plain.includes(\"─\") && BORDER_LINE_PATTERN.test(plain);\n\t}\n\n\tprivate extendBorderLine(line: string, width: number): string {\n\t\tconst remainingWidth = width - visibleWidth(line);\n\t\tif (remainingWidth <= 0) {\n\t\t\treturn line;\n\t\t}\n\t\treturn `${line}${this.borderColor(\"─\".repeat(remainingWidth))}`;\n\t}\n\n\tprivate padLine(line: string, width: number): string {\n\t\tconst remainingWidth = width - visibleWidth(line);\n\t\tif (remainingWidth <= 0) {\n\t\t\treturn line;\n\t\t}\n\t\treturn `${line}${\" \".repeat(remainingWidth)}`;\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for paste image keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
1
+ {"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,GAAG,EAAiC,MAAM,wBAAwB,CAAC;AAC/H,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEtF,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC;CACtC;AAKD;;GAEG;AACH,qBAAa,YAAa,SAAQ,MAAM;IACvC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAsC;IAClD,cAAc,EAAE,GAAG,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,CAAa;IAG3D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,2EAA2E;IACpE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAEvD,YAAY,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,mBAAmB,EAKvG;IAED,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,GAAG,IAAI,CAErE;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAqC9B;IAED,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,OAAO;IAQf;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAEzD;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAiD9B;CACD","sourcesContent":["import { Editor, type EditorOptions, type EditorTheme, type TUI, truncateToWidth, visibleWidth } from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.js\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\tpromptPrefix?: string;\n\tplaceholder?: string | (() => string);\n}\n\nconst ANSI_ESCAPE_PATTERN = /\\x1b\\[[0-?]*[ -/]*[@-~]|\\x1b\\][^\\x07]*(?:\\x07|\\x1b\\\\)|\\x1b[PX_][\\s\\S]*?\\x1b\\\\/g;\nconst BORDER_LINE_PATTERN = /^[─ ↑↓0-9more]+$/;\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate promptPrefix: string;\n\tprivate placeholder: string | (() => string) | undefined;\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tthis.promptPrefix = options?.promptPrefix ?? \"❯ \";\n\t\tthis.placeholder = options?.placeholder;\n\t}\n\n\tsetPlaceholder(placeholder: string | (() => string) | undefined): void {\n\t\tthis.placeholder = placeholder;\n\t}\n\n\trender(width: number): string[] {\n\t\tconst promptWidth = visibleWidth(this.promptPrefix);\n\t\tif (promptWidth <= 0 || width <= promptWidth + 1) {\n\t\t\treturn super.render(width);\n\t\t}\n\n\t\tconst editorWidth = Math.max(1, width - promptWidth);\n\t\tconst lines = super.render(editorWidth);\n\t\tlet borderCount = 0;\n\t\tlet inPromptBox = false;\n\t\tlet promptShown = false;\n\n\t\tconst placeholder = typeof this.placeholder === \"function\" ? this.placeholder() : this.placeholder;\n\n\t\treturn lines.map((line) => {\n\t\t\tif (this.isEditorBorderLine(line)) {\n\t\t\t\tborderCount += 1;\n\t\t\t\tif (borderCount === 1) {\n\t\t\t\t\tinPromptBox = true;\n\t\t\t\t\tpromptShown = false;\n\t\t\t\t} else if (borderCount === 2) {\n\t\t\t\t\tinPromptBox = false;\n\t\t\t\t}\n\t\t\t\treturn this.extendBorderLine(line, width);\n\t\t\t}\n\n\t\t\tconst showPrompt = inPromptBox && !promptShown;\n\t\t\tconst prefix = showPrompt ? this.promptPrefix : \" \".repeat(promptWidth);\n\t\t\tlet content = line;\n\t\t\tif (showPrompt && placeholder && this.getText() === \"\") {\n\t\t\t\tcontent = truncateToWidth(placeholder, editorWidth, \"...\");\n\t\t\t}\n\t\t\tif (inPromptBox) {\n\t\t\t\tpromptShown = true;\n\t\t\t}\n\t\t\treturn this.padLine(`${prefix}${content}`, width);\n\t\t});\n\t}\n\n\tprivate isEditorBorderLine(line: string): boolean {\n\t\tconst plain = line.replace(ANSI_ESCAPE_PATTERN, \"\").trim();\n\t\treturn plain.includes(\"─\") && BORDER_LINE_PATTERN.test(plain);\n\t}\n\n\tprivate extendBorderLine(line: string, width: number): string {\n\t\tconst remainingWidth = width - visibleWidth(line);\n\t\tif (remainingWidth <= 0) {\n\t\t\treturn line;\n\t\t}\n\t\treturn `${line}${this.borderColor(\"─\".repeat(remainingWidth))}`;\n\t}\n\n\tprivate padLine(line: string, width: number): string {\n\t\tconst remainingWidth = width - visibleWidth(line);\n\t\tif (remainingWidth <= 0) {\n\t\t\treturn line;\n\t\t}\n\t\treturn `${line}${\" \".repeat(remainingWidth)}`;\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for paste image keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { Editor, visibleWidth } from "@earendil-works/pi-tui";
1
+ import { Editor, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
2
  const ANSI_ESCAPE_PATTERN = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07]*(?:\x07|\x1b\\)|\x1b[PX_][\s\S]*?\x1b\\/g;
3
3
  const BORDER_LINE_PATTERN = /^[─ ↑↓0-9more]+$/;
4
4
  /**
@@ -10,6 +10,10 @@ export class CustomEditor extends Editor {
10
10
  this.actionHandlers = new Map();
11
11
  this.keybindings = keybindings;
12
12
  this.promptPrefix = options?.promptPrefix ?? "❯ ";
13
+ this.placeholder = options?.placeholder;
14
+ }
15
+ setPlaceholder(placeholder) {
16
+ this.placeholder = placeholder;
13
17
  }
14
18
  render(width) {
15
19
  const promptWidth = visibleWidth(this.promptPrefix);
@@ -21,6 +25,7 @@ export class CustomEditor extends Editor {
21
25
  let borderCount = 0;
22
26
  let inPromptBox = false;
23
27
  let promptShown = false;
28
+ const placeholder = typeof this.placeholder === "function" ? this.placeholder() : this.placeholder;
24
29
  return lines.map((line) => {
25
30
  if (this.isEditorBorderLine(line)) {
26
31
  borderCount += 1;
@@ -33,11 +38,16 @@ export class CustomEditor extends Editor {
33
38
  }
34
39
  return this.extendBorderLine(line, width);
35
40
  }
36
- const prefix = inPromptBox && !promptShown ? this.promptPrefix : " ".repeat(promptWidth);
41
+ const showPrompt = inPromptBox && !promptShown;
42
+ const prefix = showPrompt ? this.promptPrefix : " ".repeat(promptWidth);
43
+ let content = line;
44
+ if (showPrompt && placeholder && this.getText() === "") {
45
+ content = truncateToWidth(placeholder, editorWidth, "...");
46
+ }
37
47
  if (inPromptBox) {
38
48
  promptShown = true;
39
49
  }
40
- return this.padLine(`${prefix}${line}`, width);
50
+ return this.padLine(`${prefix}${content}`, width);
41
51
  });
42
52
  }
43
53
  isEditorBorderLine(line) {
@@ -1 +1 @@
1
- {"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import { Editor, type EditorOptions, type EditorTheme, type TUI, visibleWidth } from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.js\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\tpromptPrefix?: string;\n}\n\nconst ANSI_ESCAPE_PATTERN = /\\x1b\\[[0-?]*[ -/]*[@-~]|\\x1b\\][^\\x07]*(?:\\x07|\\x1b\\\\)|\\x1b[PX_][\\s\\S]*?\\x1b\\\\/g;\nconst BORDER_LINE_PATTERN = /^[─ ↑↓0-9more]+$/;\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate promptPrefix: string;\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tthis.promptPrefix = options?.promptPrefix ?? \"❯ \";\n\t}\n\n\trender(width: number): string[] {\n\t\tconst promptWidth = visibleWidth(this.promptPrefix);\n\t\tif (promptWidth <= 0 || width <= promptWidth + 1) {\n\t\t\treturn super.render(width);\n\t\t}\n\n\t\tconst editorWidth = Math.max(1, width - promptWidth);\n\t\tconst lines = super.render(editorWidth);\n\t\tlet borderCount = 0;\n\t\tlet inPromptBox = false;\n\t\tlet promptShown = false;\n\n\t\treturn lines.map((line) => {\n\t\t\tif (this.isEditorBorderLine(line)) {\n\t\t\t\tborderCount += 1;\n\t\t\t\tif (borderCount === 1) {\n\t\t\t\t\tinPromptBox = true;\n\t\t\t\t\tpromptShown = false;\n\t\t\t\t} else if (borderCount === 2) {\n\t\t\t\t\tinPromptBox = false;\n\t\t\t\t}\n\t\t\t\treturn this.extendBorderLine(line, width);\n\t\t\t}\n\n\t\t\tconst prefix = inPromptBox && !promptShown ? this.promptPrefix : \" \".repeat(promptWidth);\n\t\t\tif (inPromptBox) {\n\t\t\t\tpromptShown = true;\n\t\t\t}\n\t\t\treturn this.padLine(`${prefix}${line}`, width);\n\t\t});\n\t}\n\n\tprivate isEditorBorderLine(line: string): boolean {\n\t\tconst plain = line.replace(ANSI_ESCAPE_PATTERN, \"\").trim();\n\t\treturn plain.includes(\"─\") && BORDER_LINE_PATTERN.test(plain);\n\t}\n\n\tprivate extendBorderLine(line: string, width: number): string {\n\t\tconst remainingWidth = width - visibleWidth(line);\n\t\tif (remainingWidth <= 0) {\n\t\t\treturn line;\n\t\t}\n\t\treturn `${line}${this.borderColor(\"─\".repeat(remainingWidth))}`;\n\t}\n\n\tprivate padLine(line: string, width: number): string {\n\t\tconst remainingWidth = width - visibleWidth(line);\n\t\tif (remainingWidth <= 0) {\n\t\t\treturn line;\n\t\t}\n\t\treturn `${line}${\" \".repeat(remainingWidth)}`;\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for paste image keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
1
+ {"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import { Editor, type EditorOptions, type EditorTheme, type TUI, truncateToWidth, visibleWidth } from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.js\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\tpromptPrefix?: string;\n\tplaceholder?: string | (() => string);\n}\n\nconst ANSI_ESCAPE_PATTERN = /\\x1b\\[[0-?]*[ -/]*[@-~]|\\x1b\\][^\\x07]*(?:\\x07|\\x1b\\\\)|\\x1b[PX_][\\s\\S]*?\\x1b\\\\/g;\nconst BORDER_LINE_PATTERN = /^[─ ↑↓0-9more]+$/;\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate promptPrefix: string;\n\tprivate placeholder: string | (() => string) | undefined;\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tthis.promptPrefix = options?.promptPrefix ?? \"❯ \";\n\t\tthis.placeholder = options?.placeholder;\n\t}\n\n\tsetPlaceholder(placeholder: string | (() => string) | undefined): void {\n\t\tthis.placeholder = placeholder;\n\t}\n\n\trender(width: number): string[] {\n\t\tconst promptWidth = visibleWidth(this.promptPrefix);\n\t\tif (promptWidth <= 0 || width <= promptWidth + 1) {\n\t\t\treturn super.render(width);\n\t\t}\n\n\t\tconst editorWidth = Math.max(1, width - promptWidth);\n\t\tconst lines = super.render(editorWidth);\n\t\tlet borderCount = 0;\n\t\tlet inPromptBox = false;\n\t\tlet promptShown = false;\n\n\t\tconst placeholder = typeof this.placeholder === \"function\" ? this.placeholder() : this.placeholder;\n\n\t\treturn lines.map((line) => {\n\t\t\tif (this.isEditorBorderLine(line)) {\n\t\t\t\tborderCount += 1;\n\t\t\t\tif (borderCount === 1) {\n\t\t\t\t\tinPromptBox = true;\n\t\t\t\t\tpromptShown = false;\n\t\t\t\t} else if (borderCount === 2) {\n\t\t\t\t\tinPromptBox = false;\n\t\t\t\t}\n\t\t\t\treturn this.extendBorderLine(line, width);\n\t\t\t}\n\n\t\t\tconst showPrompt = inPromptBox && !promptShown;\n\t\t\tconst prefix = showPrompt ? this.promptPrefix : \" \".repeat(promptWidth);\n\t\t\tlet content = line;\n\t\t\tif (showPrompt && placeholder && this.getText() === \"\") {\n\t\t\t\tcontent = truncateToWidth(placeholder, editorWidth, \"...\");\n\t\t\t}\n\t\t\tif (inPromptBox) {\n\t\t\t\tpromptShown = true;\n\t\t\t}\n\t\t\treturn this.padLine(`${prefix}${content}`, width);\n\t\t});\n\t}\n\n\tprivate isEditorBorderLine(line: string): boolean {\n\t\tconst plain = line.replace(ANSI_ESCAPE_PATTERN, \"\").trim();\n\t\treturn plain.includes(\"─\") && BORDER_LINE_PATTERN.test(plain);\n\t}\n\n\tprivate extendBorderLine(line: string, width: number): string {\n\t\tconst remainingWidth = width - visibleWidth(line);\n\t\tif (remainingWidth <= 0) {\n\t\t\treturn line;\n\t\t}\n\t\treturn `${line}${this.borderColor(\"─\".repeat(remainingWidth))}`;\n\t}\n\n\tprivate padLine(line: string, width: number): string {\n\t\tconst remainingWidth = width - visibleWidth(line);\n\t\tif (remainingWidth <= 0) {\n\t\t\treturn line;\n\t\t}\n\t\treturn `${line}${\" \".repeat(remainingWidth)}`;\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for paste image keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EAGf,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AA4HxF;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,SAAS;IAGvC,OAAO,CAAC,OAAO;IAF3B,OAAO,CAAC,kBAAkB,CAAQ;IAElC,YAAoB,OAAO,EAAE,YAAY,EAAI;IAE7C,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9B;CACF;AAED;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAE7C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IAFpB,YACU,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B,EAC5C;IAEJ,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAE7C;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAsB9B;CACF","sourcesContent":["import {\n type Component,\n truncateToWidth,\n visibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n // Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n return text\n .replace(/[\\r\\n\\t]/g, \" \")\n .replace(/ +/g, \" \")\n .trim();\n}\n\n/**\n * Format token counts (similar to web-ui)\n */\nfunction formatTokens(count: number): string {\n if (count < 1000) return count.toString();\n if (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n if (count < 1000000) return `${Math.round(count / 1000)}k`;\n if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n return `${Math.round(count / 1000000)}M`;\n}\n\nfunction replaceHome(input: string): string {\n const home = process.env.HOME || process.env.USERPROFILE;\n if (home && input.startsWith(home)) {\n return `~${input.slice(home.length)}`;\n }\n return input;\n}\n\nfunction rightAlign(line: string, width: number): string {\n const lineWidth = visibleWidth(line);\n if (lineWidth >= width) {\n return truncateToWidth(line, width, theme.fg(\"dim\", \"...\"));\n }\n return `${\" \".repeat(width - lineWidth)}${line}`;\n}\n\nfunction getUsageLine(\n session: AgentSession,\n autoCompactEnabled: boolean,\n width: number,\n): string {\n const state = session.state;\n\n // Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n let totalInput = 0;\n let totalOutput = 0;\n let totalCacheRead = 0;\n let totalCacheWrite = 0;\n let totalCost = 0;\n\n for (const entry of session.sessionManager.getEntries()) {\n if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n totalInput += entry.message.usage.input;\n totalOutput += entry.message.usage.output;\n totalCacheRead += entry.message.usage.cacheRead;\n totalCacheWrite += entry.message.usage.cacheWrite;\n totalCost += entry.message.usage.cost.total;\n }\n }\n\n // Calculate context usage from session (handles compaction correctly).\n // After compaction, tokens are unknown until the next LLM response.\n const contextUsage = session.getContextUsage();\n const contextWindow =\n contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n const contextPercentValue = contextUsage?.percent ?? 0;\n const contextPercent =\n contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n const usageParts = [];\n if (totalInput)\n usageParts.push(\n `${theme.fg(\"dim\", \"↑\")}${theme.fg(\"muted\", formatTokens(totalInput))}`,\n );\n if (totalOutput)\n usageParts.push(\n `${theme.fg(\"dim\", \"↓\")}${theme.fg(\"muted\", formatTokens(totalOutput))}`,\n );\n if (totalCacheRead)\n usageParts.push(\n `${theme.fg(\"dim\", \"R\")}${theme.fg(\"muted\", formatTokens(totalCacheRead))}`,\n );\n if (totalCacheWrite)\n usageParts.push(\n `${theme.fg(\"dim\", \"W\")}${theme.fg(\"muted\", formatTokens(totalCacheWrite))}`,\n );\n\n // Show cost with \"(sub)\" indicator if using OAuth subscription\n const usingSubscription = state.model\n ? session.modelRegistry.isUsingOAuth(state.model)\n : false;\n if (totalCost || usingSubscription) {\n usageParts.push(\n `${theme.fg(\"muted\", `$${totalCost.toFixed(3)}`)}${usingSubscription ? ` ${theme.fg(\"dim\", \"(sub)\")}` : \"\"}`,\n );\n }\n\n const autoIndicator = autoCompactEnabled ? \" (auto)\" : \"\";\n const contextPercentDisplay =\n contextPercent === \"?\"\n ? `?/${formatTokens(contextWindow)}${autoIndicator}`\n : `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n if (contextPercentValue > 90) {\n usageParts.push(theme.fg(\"error\", contextPercentDisplay));\n } else if (contextPercentValue > 70) {\n usageParts.push(theme.fg(\"warning\", contextPercentDisplay));\n } else {\n usageParts.push(theme.fg(\"muted\", contextPercentDisplay));\n }\n\n const separator = theme.fg(\"dim\", \" • \");\n const usageText =\n usageParts.length > 0\n ? usageParts.join(separator)\n : theme.fg(\"muted\", contextPercentDisplay);\n return rightAlign(usageText, width);\n}\n\n/**\n * Right-aligned usage meter that sits above the composer, matching the approved\n * prototype's separate token/cost/context ribbon.\n */\nexport class UsageMeterComponent implements Component {\n private autoCompactEnabled = true;\n\n constructor(private session: AgentSession) {}\n\n setSession(session: AgentSession): void {\n this.session = session;\n }\n\n setAutoCompactEnabled(enabled: boolean): void {\n this.autoCompactEnabled = enabled;\n }\n\n invalidate(): void {\n // Render pulls live session data.\n }\n\n render(width: number): string[] {\n return [getUsageLine(this.session, this.autoCompactEnabled, width)];\n }\n}\n\n/**\n * Sparse statusline below the composer. It mirrors the preview: model + cwd\n * when idle, or one semantic dot with short recovery copy while work is live.\n */\nexport class FooterComponent implements Component {\n constructor(\n private session: AgentSession,\n private footerData: ReadonlyFooterDataProvider,\n ) {}\n\n setSession(session: AgentSession): void {\n this.session = session;\n }\n\n setAutoCompactEnabled(_enabled: boolean): void {\n // Usage state lives in UsageMeterComponent. Kept for compatibility with existing call sites.\n }\n\n /**\n * No-op: git branch caching now handled by provider.\n * Kept for compatibility with existing call sites in interactive-mode.\n */\n invalidate(): void {\n // No-op: git branch is cached/invalidated by provider\n }\n\n /**\n * Clean up resources.\n * Git watcher cleanup now handled by provider.\n */\n dispose(): void {\n // Git watcher cleanup handled by provider\n }\n\n render(width: number): string[] {\n const state = this.session.state;\n const pwd = replaceHome(this.session.sessionManager.getCwd());\n\n const modelName = state.model?.id || \"no-model\";\n let modelLabel = modelName;\n if (state.model?.reasoning) {\n const thinkingLevel = state.thinkingLevel || \"off\";\n modelLabel =\n thinkingLevel === \"off\" ? modelName : `${modelName} ${thinkingLevel}`;\n }\n if (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n modelLabel = `(${state.model.provider}) ${modelLabel}`;\n }\n\n const liveState = this.session.isStreaming\n ? theme.fg(\"muted\", \"Esc to interrupt\")\n : undefined;\n const statusText =\n liveState ??\n `${theme.fg(\"dim\", modelLabel)} ${theme.fg(\"dim\", \"•\")} ${theme.fg(\"muted\", pwd)}`;\n return [truncateToWidth(statusText, width, theme.fg(\"dim\", \"...\"))];\n }\n}\n"]}
1
+ {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EAGf,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AA4HxF;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,SAAS;IAGvC,OAAO,CAAC,OAAO;IAF3B,OAAO,CAAC,kBAAkB,CAAQ;IAElC,YAAoB,OAAO,EAAE,YAAY,EAAI;IAE7C,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9B;CACF;AAED;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAE7C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IAFpB,YACU,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B,EAC5C;IAEJ,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAE7C;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAsB9B;CACF","sourcesContent":["import {\n type Component,\n truncateToWidth,\n visibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n // Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n return text\n .replace(/[\\r\\n\\t]/g, \" \")\n .replace(/ +/g, \" \")\n .trim();\n}\n\n/**\n * Format token counts (similar to web-ui)\n */\nfunction formatTokens(count: number): string {\n if (count < 1000) return count.toString();\n if (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n if (count < 1000000) return `${Math.round(count / 1000)}k`;\n if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n return `${Math.round(count / 1000000)}M`;\n}\n\nfunction replaceHome(input: string): string {\n const home = process.env.HOME || process.env.USERPROFILE;\n if (home && input.startsWith(home)) {\n return `~${input.slice(home.length)}`;\n }\n return input;\n}\n\nfunction rightAlign(line: string, width: number): string {\n const lineWidth = visibleWidth(line);\n if (lineWidth >= width) {\n return truncateToWidth(line, width, theme.fg(\"dim\", \"...\"));\n }\n return `${\" \".repeat(width - lineWidth)}${line}`;\n}\n\nfunction getUsageLine(\n session: AgentSession,\n autoCompactEnabled: boolean,\n width: number,\n): string {\n const state = session.state;\n\n // Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n let totalInput = 0;\n let totalOutput = 0;\n let totalCacheRead = 0;\n let totalCacheWrite = 0;\n let totalCost = 0;\n\n for (const entry of session.sessionManager.getEntries()) {\n if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n totalInput += entry.message.usage.input;\n totalOutput += entry.message.usage.output;\n totalCacheRead += entry.message.usage.cacheRead;\n totalCacheWrite += entry.message.usage.cacheWrite;\n totalCost += entry.message.usage.cost.total;\n }\n }\n\n // Calculate context usage from session (handles compaction correctly).\n // After compaction, tokens are unknown until the next LLM response.\n const contextUsage = session.getContextUsage();\n const contextWindow =\n contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n const contextPercentValue = contextUsage?.percent ?? 0;\n const contextPercent =\n contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n const usageParts = [];\n if (totalInput)\n usageParts.push(\n `${theme.fg(\"dim\", \"↑\")}${theme.fg(\"muted\", formatTokens(totalInput))}`,\n );\n if (totalOutput)\n usageParts.push(\n `${theme.fg(\"dim\", \"↓\")}${theme.fg(\"muted\", formatTokens(totalOutput))}`,\n );\n if (totalCacheRead)\n usageParts.push(\n `${theme.fg(\"dim\", \"R\")}${theme.fg(\"muted\", formatTokens(totalCacheRead))}`,\n );\n if (totalCacheWrite)\n usageParts.push(\n `${theme.fg(\"dim\", \"W\")}${theme.fg(\"muted\", formatTokens(totalCacheWrite))}`,\n );\n\n // Show cost with \"(sub)\" indicator if using OAuth subscription\n const usingSubscription = state.model\n ? session.modelRegistry.isUsingOAuth(state.model)\n : false;\n if (totalCost || usingSubscription) {\n usageParts.push(\n `${theme.fg(\"muted\", `$${totalCost.toFixed(3)}`)}${usingSubscription ? ` ${theme.fg(\"dim\", \"(sub)\")}` : \"\"}`,\n );\n }\n\n const autoIndicator = autoCompactEnabled ? \" (auto)\" : \"\";\n const contextPercentDisplay =\n contextPercent === \"?\"\n ? `?/${formatTokens(contextWindow)}${autoIndicator}`\n : `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n if (contextPercentValue > 90) {\n usageParts.push(theme.fg(\"error\", contextPercentDisplay));\n } else if (contextPercentValue > 70) {\n usageParts.push(theme.fg(\"warning\", contextPercentDisplay));\n } else {\n usageParts.push(theme.fg(\"muted\", contextPercentDisplay));\n }\n\n const separator = theme.fg(\"dim\", \" • \");\n const usageText =\n usageParts.length > 0\n ? usageParts.join(separator)\n : theme.fg(\"muted\", contextPercentDisplay);\n return rightAlign(usageText, width);\n}\n\n/**\n * Right-aligned usage meter that sits above the composer, matching the approved\n * prototype's separate token/cost/context ribbon.\n */\nexport class UsageMeterComponent implements Component {\n private autoCompactEnabled = true;\n\n constructor(private session: AgentSession) {}\n\n setSession(session: AgentSession): void {\n this.session = session;\n }\n\n setAutoCompactEnabled(enabled: boolean): void {\n this.autoCompactEnabled = enabled;\n }\n\n invalidate(): void {\n // Render pulls live session data.\n }\n\n render(width: number): string[] {\n return [getUsageLine(this.session, this.autoCompactEnabled, width)];\n }\n}\n\n/**\n * Sparse statusline below the composer. It mirrors the preview: model + cwd\n * when idle, or one semantic dot with short recovery copy while work is live.\n */\nexport class FooterComponent implements Component {\n constructor(\n private session: AgentSession,\n private footerData: ReadonlyFooterDataProvider,\n ) {}\n\n setSession(session: AgentSession): void {\n this.session = session;\n }\n\n setAutoCompactEnabled(_enabled: boolean): void {\n // Usage state lives in UsageMeterComponent. Kept for compatibility with existing call sites.\n }\n\n /**\n * No-op: git branch caching now handled by provider.\n * Kept for compatibility with existing call sites in interactive-mode.\n */\n invalidate(): void {\n // No-op: git branch is cached/invalidated by provider\n }\n\n /**\n * Clean up resources.\n * Git watcher cleanup now handled by provider.\n */\n dispose(): void {\n // Git watcher cleanup handled by provider\n }\n\n render(width: number): string[] {\n const state = this.session.state;\n const pwd = replaceHome(this.session.sessionManager.getCwd());\n\n const modelName = state.model?.id || \"no-model\";\n let modelLabel = modelName;\n if (state.model?.reasoning) {\n const thinkingLevel = state.thinkingLevel || \"off\";\n modelLabel =\n thinkingLevel === \"off\" ? modelName : `${modelName} ${thinkingLevel}`;\n }\n if (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n modelLabel = `(${state.model.provider}) ${modelLabel}`;\n }\n\n const liveState = this.session.isStreaming\n ? theme.fg(\"muted\", \"esc to interrupt\")\n : undefined;\n const statusText =\n liveState ??\n `${theme.fg(\"dim\", modelLabel)} ${theme.fg(\"dim\", \"•\")} ${theme.fg(\"muted\", pwd)}`;\n return [truncateToWidth(statusText, width, theme.fg(\"dim\", \"...\"))];\n }\n}\n"]}
@@ -162,7 +162,7 @@ export class FooterComponent {
162
162
  modelLabel = `(${state.model.provider}) ${modelLabel}`;
163
163
  }
164
164
  const liveState = this.session.isStreaming
165
- ? theme.fg("muted", "Esc to interrupt")
165
+ ? theme.fg("muted", "esc to interrupt")
166
166
  : undefined;
167
167
  const statusText = liveState ??
168
168
  `${theme.fg("dim", modelLabel)} ${theme.fg("dim", "•")} ${theme.fg("muted", pwd)}`;
@@ -1 +1 @@
1
- {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Component,\n truncateToWidth,\n visibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n // Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n return text\n .replace(/[\\r\\n\\t]/g, \" \")\n .replace(/ +/g, \" \")\n .trim();\n}\n\n/**\n * Format token counts (similar to web-ui)\n */\nfunction formatTokens(count: number): string {\n if (count < 1000) return count.toString();\n if (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n if (count < 1000000) return `${Math.round(count / 1000)}k`;\n if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n return `${Math.round(count / 1000000)}M`;\n}\n\nfunction replaceHome(input: string): string {\n const home = process.env.HOME || process.env.USERPROFILE;\n if (home && input.startsWith(home)) {\n return `~${input.slice(home.length)}`;\n }\n return input;\n}\n\nfunction rightAlign(line: string, width: number): string {\n const lineWidth = visibleWidth(line);\n if (lineWidth >= width) {\n return truncateToWidth(line, width, theme.fg(\"dim\", \"...\"));\n }\n return `${\" \".repeat(width - lineWidth)}${line}`;\n}\n\nfunction getUsageLine(\n session: AgentSession,\n autoCompactEnabled: boolean,\n width: number,\n): string {\n const state = session.state;\n\n // Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n let totalInput = 0;\n let totalOutput = 0;\n let totalCacheRead = 0;\n let totalCacheWrite = 0;\n let totalCost = 0;\n\n for (const entry of session.sessionManager.getEntries()) {\n if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n totalInput += entry.message.usage.input;\n totalOutput += entry.message.usage.output;\n totalCacheRead += entry.message.usage.cacheRead;\n totalCacheWrite += entry.message.usage.cacheWrite;\n totalCost += entry.message.usage.cost.total;\n }\n }\n\n // Calculate context usage from session (handles compaction correctly).\n // After compaction, tokens are unknown until the next LLM response.\n const contextUsage = session.getContextUsage();\n const contextWindow =\n contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n const contextPercentValue = contextUsage?.percent ?? 0;\n const contextPercent =\n contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n const usageParts = [];\n if (totalInput)\n usageParts.push(\n `${theme.fg(\"dim\", \"↑\")}${theme.fg(\"muted\", formatTokens(totalInput))}`,\n );\n if (totalOutput)\n usageParts.push(\n `${theme.fg(\"dim\", \"↓\")}${theme.fg(\"muted\", formatTokens(totalOutput))}`,\n );\n if (totalCacheRead)\n usageParts.push(\n `${theme.fg(\"dim\", \"R\")}${theme.fg(\"muted\", formatTokens(totalCacheRead))}`,\n );\n if (totalCacheWrite)\n usageParts.push(\n `${theme.fg(\"dim\", \"W\")}${theme.fg(\"muted\", formatTokens(totalCacheWrite))}`,\n );\n\n // Show cost with \"(sub)\" indicator if using OAuth subscription\n const usingSubscription = state.model\n ? session.modelRegistry.isUsingOAuth(state.model)\n : false;\n if (totalCost || usingSubscription) {\n usageParts.push(\n `${theme.fg(\"muted\", `$${totalCost.toFixed(3)}`)}${usingSubscription ? ` ${theme.fg(\"dim\", \"(sub)\")}` : \"\"}`,\n );\n }\n\n const autoIndicator = autoCompactEnabled ? \" (auto)\" : \"\";\n const contextPercentDisplay =\n contextPercent === \"?\"\n ? `?/${formatTokens(contextWindow)}${autoIndicator}`\n : `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n if (contextPercentValue > 90) {\n usageParts.push(theme.fg(\"error\", contextPercentDisplay));\n } else if (contextPercentValue > 70) {\n usageParts.push(theme.fg(\"warning\", contextPercentDisplay));\n } else {\n usageParts.push(theme.fg(\"muted\", contextPercentDisplay));\n }\n\n const separator = theme.fg(\"dim\", \" • \");\n const usageText =\n usageParts.length > 0\n ? usageParts.join(separator)\n : theme.fg(\"muted\", contextPercentDisplay);\n return rightAlign(usageText, width);\n}\n\n/**\n * Right-aligned usage meter that sits above the composer, matching the approved\n * prototype's separate token/cost/context ribbon.\n */\nexport class UsageMeterComponent implements Component {\n private autoCompactEnabled = true;\n\n constructor(private session: AgentSession) {}\n\n setSession(session: AgentSession): void {\n this.session = session;\n }\n\n setAutoCompactEnabled(enabled: boolean): void {\n this.autoCompactEnabled = enabled;\n }\n\n invalidate(): void {\n // Render pulls live session data.\n }\n\n render(width: number): string[] {\n return [getUsageLine(this.session, this.autoCompactEnabled, width)];\n }\n}\n\n/**\n * Sparse statusline below the composer. It mirrors the preview: model + cwd\n * when idle, or one semantic dot with short recovery copy while work is live.\n */\nexport class FooterComponent implements Component {\n constructor(\n private session: AgentSession,\n private footerData: ReadonlyFooterDataProvider,\n ) {}\n\n setSession(session: AgentSession): void {\n this.session = session;\n }\n\n setAutoCompactEnabled(_enabled: boolean): void {\n // Usage state lives in UsageMeterComponent. Kept for compatibility with existing call sites.\n }\n\n /**\n * No-op: git branch caching now handled by provider.\n * Kept for compatibility with existing call sites in interactive-mode.\n */\n invalidate(): void {\n // No-op: git branch is cached/invalidated by provider\n }\n\n /**\n * Clean up resources.\n * Git watcher cleanup now handled by provider.\n */\n dispose(): void {\n // Git watcher cleanup handled by provider\n }\n\n render(width: number): string[] {\n const state = this.session.state;\n const pwd = replaceHome(this.session.sessionManager.getCwd());\n\n const modelName = state.model?.id || \"no-model\";\n let modelLabel = modelName;\n if (state.model?.reasoning) {\n const thinkingLevel = state.thinkingLevel || \"off\";\n modelLabel =\n thinkingLevel === \"off\" ? modelName : `${modelName} ${thinkingLevel}`;\n }\n if (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n modelLabel = `(${state.model.provider}) ${modelLabel}`;\n }\n\n const liveState = this.session.isStreaming\n ? theme.fg(\"muted\", \"Esc to interrupt\")\n : undefined;\n const statusText =\n liveState ??\n `${theme.fg(\"dim\", modelLabel)} ${theme.fg(\"dim\", \"•\")} ${theme.fg(\"muted\", pwd)}`;\n return [truncateToWidth(statusText, width, theme.fg(\"dim\", \"...\"))];\n }\n}\n"]}
1
+ {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Component,\n truncateToWidth,\n visibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n // Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n return text\n .replace(/[\\r\\n\\t]/g, \" \")\n .replace(/ +/g, \" \")\n .trim();\n}\n\n/**\n * Format token counts (similar to web-ui)\n */\nfunction formatTokens(count: number): string {\n if (count < 1000) return count.toString();\n if (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n if (count < 1000000) return `${Math.round(count / 1000)}k`;\n if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n return `${Math.round(count / 1000000)}M`;\n}\n\nfunction replaceHome(input: string): string {\n const home = process.env.HOME || process.env.USERPROFILE;\n if (home && input.startsWith(home)) {\n return `~${input.slice(home.length)}`;\n }\n return input;\n}\n\nfunction rightAlign(line: string, width: number): string {\n const lineWidth = visibleWidth(line);\n if (lineWidth >= width) {\n return truncateToWidth(line, width, theme.fg(\"dim\", \"...\"));\n }\n return `${\" \".repeat(width - lineWidth)}${line}`;\n}\n\nfunction getUsageLine(\n session: AgentSession,\n autoCompactEnabled: boolean,\n width: number,\n): string {\n const state = session.state;\n\n // Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n let totalInput = 0;\n let totalOutput = 0;\n let totalCacheRead = 0;\n let totalCacheWrite = 0;\n let totalCost = 0;\n\n for (const entry of session.sessionManager.getEntries()) {\n if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n totalInput += entry.message.usage.input;\n totalOutput += entry.message.usage.output;\n totalCacheRead += entry.message.usage.cacheRead;\n totalCacheWrite += entry.message.usage.cacheWrite;\n totalCost += entry.message.usage.cost.total;\n }\n }\n\n // Calculate context usage from session (handles compaction correctly).\n // After compaction, tokens are unknown until the next LLM response.\n const contextUsage = session.getContextUsage();\n const contextWindow =\n contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n const contextPercentValue = contextUsage?.percent ?? 0;\n const contextPercent =\n contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n const usageParts = [];\n if (totalInput)\n usageParts.push(\n `${theme.fg(\"dim\", \"↑\")}${theme.fg(\"muted\", formatTokens(totalInput))}`,\n );\n if (totalOutput)\n usageParts.push(\n `${theme.fg(\"dim\", \"↓\")}${theme.fg(\"muted\", formatTokens(totalOutput))}`,\n );\n if (totalCacheRead)\n usageParts.push(\n `${theme.fg(\"dim\", \"R\")}${theme.fg(\"muted\", formatTokens(totalCacheRead))}`,\n );\n if (totalCacheWrite)\n usageParts.push(\n `${theme.fg(\"dim\", \"W\")}${theme.fg(\"muted\", formatTokens(totalCacheWrite))}`,\n );\n\n // Show cost with \"(sub)\" indicator if using OAuth subscription\n const usingSubscription = state.model\n ? session.modelRegistry.isUsingOAuth(state.model)\n : false;\n if (totalCost || usingSubscription) {\n usageParts.push(\n `${theme.fg(\"muted\", `$${totalCost.toFixed(3)}`)}${usingSubscription ? ` ${theme.fg(\"dim\", \"(sub)\")}` : \"\"}`,\n );\n }\n\n const autoIndicator = autoCompactEnabled ? \" (auto)\" : \"\";\n const contextPercentDisplay =\n contextPercent === \"?\"\n ? `?/${formatTokens(contextWindow)}${autoIndicator}`\n : `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n if (contextPercentValue > 90) {\n usageParts.push(theme.fg(\"error\", contextPercentDisplay));\n } else if (contextPercentValue > 70) {\n usageParts.push(theme.fg(\"warning\", contextPercentDisplay));\n } else {\n usageParts.push(theme.fg(\"muted\", contextPercentDisplay));\n }\n\n const separator = theme.fg(\"dim\", \" • \");\n const usageText =\n usageParts.length > 0\n ? usageParts.join(separator)\n : theme.fg(\"muted\", contextPercentDisplay);\n return rightAlign(usageText, width);\n}\n\n/**\n * Right-aligned usage meter that sits above the composer, matching the approved\n * prototype's separate token/cost/context ribbon.\n */\nexport class UsageMeterComponent implements Component {\n private autoCompactEnabled = true;\n\n constructor(private session: AgentSession) {}\n\n setSession(session: AgentSession): void {\n this.session = session;\n }\n\n setAutoCompactEnabled(enabled: boolean): void {\n this.autoCompactEnabled = enabled;\n }\n\n invalidate(): void {\n // Render pulls live session data.\n }\n\n render(width: number): string[] {\n return [getUsageLine(this.session, this.autoCompactEnabled, width)];\n }\n}\n\n/**\n * Sparse statusline below the composer. It mirrors the preview: model + cwd\n * when idle, or one semantic dot with short recovery copy while work is live.\n */\nexport class FooterComponent implements Component {\n constructor(\n private session: AgentSession,\n private footerData: ReadonlyFooterDataProvider,\n ) {}\n\n setSession(session: AgentSession): void {\n this.session = session;\n }\n\n setAutoCompactEnabled(_enabled: boolean): void {\n // Usage state lives in UsageMeterComponent. Kept for compatibility with existing call sites.\n }\n\n /**\n * No-op: git branch caching now handled by provider.\n * Kept for compatibility with existing call sites in interactive-mode.\n */\n invalidate(): void {\n // No-op: git branch is cached/invalidated by provider\n }\n\n /**\n * Clean up resources.\n * Git watcher cleanup now handled by provider.\n */\n dispose(): void {\n // Git watcher cleanup handled by provider\n }\n\n render(width: number): string[] {\n const state = this.session.state;\n const pwd = replaceHome(this.session.sessionManager.getCwd());\n\n const modelName = state.model?.id || \"no-model\";\n let modelLabel = modelName;\n if (state.model?.reasoning) {\n const thinkingLevel = state.thinkingLevel || \"off\";\n modelLabel =\n thinkingLevel === \"off\" ? modelName : `${modelName} ${thinkingLevel}`;\n }\n if (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n modelLabel = `(${state.model.provider}) ${modelLabel}`;\n }\n\n const liveState = this.session.isStreaming\n ? theme.fg(\"muted\", \"esc to interrupt\")\n : undefined;\n const statusText =\n liveState ??\n `${theme.fg(\"dim\", modelLabel)} ${theme.fg(\"dim\", \"•\")} ${theme.fg(\"muted\", pwd)}`;\n return [truncateToWidth(statusText, width, theme.fg(\"dim\", \"...\"))];\n }\n}\n"]}
@@ -14,7 +14,7 @@ export { DynamicBorder } from "./dynamic-border.js";
14
14
  export { ExtensionEditorComponent } from "./extension-editor.js";
15
15
  export { ExtensionInputComponent } from "./extension-input.js";
16
16
  export { ExtensionSelectorComponent } from "./extension-selector.js";
17
- export { FooterComponent } from "./footer.js";
17
+ export { FooterComponent, UsageMeterComponent } from "./footer.js";
18
18
  export { keyHint, keyText, rawKeyHint } from "./keybinding-hints.js";
19
19
  export { LoginDialogComponent } from "./login-dialog.js";
20
20
  export { ModelSelectorComponent } from "./model-selector.js";
@@ -30,5 +30,6 @@ export { ToolExecutionComponent, type ToolExecutionOptions } from "./tool-execut
30
30
  export { TreeSelectorComponent } from "./tree-selector.js";
31
31
  export { UserMessageComponent } from "./user-message.js";
32
32
  export { UserMessageSelectorComponent } from "./user-message-selector.js";
33
+ export { WorkingStatusComponent, type WorkingStatusComponentOptions } from "./working-status.js";
33
34
  export { truncateToVisualLines, type VisualTruncateResult } from "./visual-truncate.js";
34
35
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,EACzB,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,GAC9B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,iCAAiC,EACjC,2BAA2B,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,iCAAiC,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,KAAK,iBAAiB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AACrH,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAChH,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,+BAA+B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC","sourcesContent":["// UI Components for extensions\nexport { ArminComponent } from \"./armin.js\";\nexport { AssistantMessageComponent } from \"./assistant-message.js\";\nexport { BashExecutionComponent } from \"./bash-execution.js\";\nexport { BorderedLoader } from \"./bordered-loader.js\";\nexport {\n chatEntriesFromAgentMessages,\n LiveChatEntriesController,\n renderChatMessageEntry,\n type ChatMessageEntry,\n type ChatMessageRenderOptions,\n} from \"./chat-message-renderer.js\";\nexport {\n addChatTranscriptEntry,\n ChatTranscriptComponent,\n ScrollableChatTranscriptComponent,\n ScrollableComponentViewport,\n type ChatTranscriptEntryLike,\n type ChatTranscriptRenderer,\n type ChatTranscriptRole,\n} from \"./chat-transcript.js\";\nexport { BranchSummaryMessageComponent } from \"./branch-summary-message.js\";\nexport { CompactionSummaryMessageComponent } from \"./compaction-summary-message.js\";\nexport { CustomEditor } from \"./custom-editor.js\";\nexport { CustomMessageComponent } from \"./custom-message.js\";\nexport { DaxnutsComponent } from \"./daxnuts.js\";\nexport { type RenderDiffOptions, renderDiff } from \"./diff.js\";\nexport { DynamicBorder } from \"./dynamic-border.js\";\nexport { ExtensionEditorComponent } from \"./extension-editor.js\";\nexport { ExtensionInputComponent } from \"./extension-input.js\";\nexport { ExtensionSelectorComponent } from \"./extension-selector.js\";\nexport { FooterComponent } from \"./footer.js\";\nexport { keyHint, keyText, rawKeyHint } from \"./keybinding-hints.js\";\nexport { LoginDialogComponent } from \"./login-dialog.js\";\nexport { ModelSelectorComponent } from \"./model-selector.js\";\nexport { OAuthSelectorComponent } from \"./oauth-selector.js\";\nexport { type ModelsCallbacks, type ModelsConfig, ScopedModelsSelectorComponent } from \"./scoped-models-selector.js\";\nexport { SessionSelectorComponent } from \"./session-selector.js\";\nexport { type SettingsCallbacks, type SettingsConfig, SettingsSelectorComponent } from \"./settings-selector.js\";\nexport { ShowImagesSelectorComponent } from \"./show-images-selector.js\";\nexport { SkillInvocationMessageComponent } from \"./skill-invocation-message.js\";\nexport { ThemeSelectorComponent } from \"./theme-selector.js\";\nexport { ThinkingSelectorComponent } from \"./thinking-selector.js\";\nexport { ToolExecutionComponent, type ToolExecutionOptions } from \"./tool-execution.js\";\nexport { TreeSelectorComponent } from \"./tree-selector.js\";\nexport { UserMessageComponent } from \"./user-message.js\";\nexport { UserMessageSelectorComponent } from \"./user-message-selector.js\";\nexport { truncateToVisualLines, type VisualTruncateResult } from \"./visual-truncate.js\";\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,EACzB,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,GAC9B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,iCAAiC,EACjC,2BAA2B,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,iCAAiC,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,KAAK,iBAAiB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AACrH,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAChH,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,+BAA+B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AACjG,OAAO,EAAE,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC","sourcesContent":["// UI Components for extensions\nexport { ArminComponent } from \"./armin.js\";\nexport { AssistantMessageComponent } from \"./assistant-message.js\";\nexport { BashExecutionComponent } from \"./bash-execution.js\";\nexport { BorderedLoader } from \"./bordered-loader.js\";\nexport {\n chatEntriesFromAgentMessages,\n LiveChatEntriesController,\n renderChatMessageEntry,\n type ChatMessageEntry,\n type ChatMessageRenderOptions,\n} from \"./chat-message-renderer.js\";\nexport {\n addChatTranscriptEntry,\n ChatTranscriptComponent,\n ScrollableChatTranscriptComponent,\n ScrollableComponentViewport,\n type ChatTranscriptEntryLike,\n type ChatTranscriptRenderer,\n type ChatTranscriptRole,\n} from \"./chat-transcript.js\";\nexport { BranchSummaryMessageComponent } from \"./branch-summary-message.js\";\nexport { CompactionSummaryMessageComponent } from \"./compaction-summary-message.js\";\nexport { CustomEditor } from \"./custom-editor.js\";\nexport { CustomMessageComponent } from \"./custom-message.js\";\nexport { DaxnutsComponent } from \"./daxnuts.js\";\nexport { type RenderDiffOptions, renderDiff } from \"./diff.js\";\nexport { DynamicBorder } from \"./dynamic-border.js\";\nexport { ExtensionEditorComponent } from \"./extension-editor.js\";\nexport { ExtensionInputComponent } from \"./extension-input.js\";\nexport { ExtensionSelectorComponent } from \"./extension-selector.js\";\nexport { FooterComponent, UsageMeterComponent } from \"./footer.js\";\nexport { keyHint, keyText, rawKeyHint } from \"./keybinding-hints.js\";\nexport { LoginDialogComponent } from \"./login-dialog.js\";\nexport { ModelSelectorComponent } from \"./model-selector.js\";\nexport { OAuthSelectorComponent } from \"./oauth-selector.js\";\nexport { type ModelsCallbacks, type ModelsConfig, ScopedModelsSelectorComponent } from \"./scoped-models-selector.js\";\nexport { SessionSelectorComponent } from \"./session-selector.js\";\nexport { type SettingsCallbacks, type SettingsConfig, SettingsSelectorComponent } from \"./settings-selector.js\";\nexport { ShowImagesSelectorComponent } from \"./show-images-selector.js\";\nexport { SkillInvocationMessageComponent } from \"./skill-invocation-message.js\";\nexport { ThemeSelectorComponent } from \"./theme-selector.js\";\nexport { ThinkingSelectorComponent } from \"./thinking-selector.js\";\nexport { ToolExecutionComponent, type ToolExecutionOptions } from \"./tool-execution.js\";\nexport { TreeSelectorComponent } from \"./tree-selector.js\";\nexport { UserMessageComponent } from \"./user-message.js\";\nexport { UserMessageSelectorComponent } from \"./user-message-selector.js\";\nexport { WorkingStatusComponent, type WorkingStatusComponentOptions } from \"./working-status.js\";\nexport { truncateToVisualLines, type VisualTruncateResult } from \"./visual-truncate.js\";\n"]}
@@ -15,7 +15,7 @@ export { DynamicBorder } from "./dynamic-border.js";
15
15
  export { ExtensionEditorComponent } from "./extension-editor.js";
16
16
  export { ExtensionInputComponent } from "./extension-input.js";
17
17
  export { ExtensionSelectorComponent } from "./extension-selector.js";
18
- export { FooterComponent } from "./footer.js";
18
+ export { FooterComponent, UsageMeterComponent } from "./footer.js";
19
19
  export { keyHint, keyText, rawKeyHint } from "./keybinding-hints.js";
20
20
  export { LoginDialogComponent } from "./login-dialog.js";
21
21
  export { ModelSelectorComponent } from "./model-selector.js";
@@ -31,5 +31,6 @@ export { ToolExecutionComponent } from "./tool-execution.js";
31
31
  export { TreeSelectorComponent } from "./tree-selector.js";
32
32
  export { UserMessageComponent } from "./user-message.js";
33
33
  export { UserMessageSelectorComponent } from "./user-message-selector.js";
34
+ export { WorkingStatusComponent } from "./working-status.js";
34
35
  export { truncateToVisualLines } from "./visual-truncate.js";
35
36
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/index.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,EACzB,sBAAsB,GAGvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,iCAAiC,EACjC,2BAA2B,GAI5B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,iCAAiC,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAA0B,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAA2C,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AACrH,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAA+C,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAChH,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,+BAA+B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAA6B,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAA6B,MAAM,sBAAsB,CAAC","sourcesContent":["// UI Components for extensions\nexport { ArminComponent } from \"./armin.js\";\nexport { AssistantMessageComponent } from \"./assistant-message.js\";\nexport { BashExecutionComponent } from \"./bash-execution.js\";\nexport { BorderedLoader } from \"./bordered-loader.js\";\nexport {\n chatEntriesFromAgentMessages,\n LiveChatEntriesController,\n renderChatMessageEntry,\n type ChatMessageEntry,\n type ChatMessageRenderOptions,\n} from \"./chat-message-renderer.js\";\nexport {\n addChatTranscriptEntry,\n ChatTranscriptComponent,\n ScrollableChatTranscriptComponent,\n ScrollableComponentViewport,\n type ChatTranscriptEntryLike,\n type ChatTranscriptRenderer,\n type ChatTranscriptRole,\n} from \"./chat-transcript.js\";\nexport { BranchSummaryMessageComponent } from \"./branch-summary-message.js\";\nexport { CompactionSummaryMessageComponent } from \"./compaction-summary-message.js\";\nexport { CustomEditor } from \"./custom-editor.js\";\nexport { CustomMessageComponent } from \"./custom-message.js\";\nexport { DaxnutsComponent } from \"./daxnuts.js\";\nexport { type RenderDiffOptions, renderDiff } from \"./diff.js\";\nexport { DynamicBorder } from \"./dynamic-border.js\";\nexport { ExtensionEditorComponent } from \"./extension-editor.js\";\nexport { ExtensionInputComponent } from \"./extension-input.js\";\nexport { ExtensionSelectorComponent } from \"./extension-selector.js\";\nexport { FooterComponent } from \"./footer.js\";\nexport { keyHint, keyText, rawKeyHint } from \"./keybinding-hints.js\";\nexport { LoginDialogComponent } from \"./login-dialog.js\";\nexport { ModelSelectorComponent } from \"./model-selector.js\";\nexport { OAuthSelectorComponent } from \"./oauth-selector.js\";\nexport { type ModelsCallbacks, type ModelsConfig, ScopedModelsSelectorComponent } from \"./scoped-models-selector.js\";\nexport { SessionSelectorComponent } from \"./session-selector.js\";\nexport { type SettingsCallbacks, type SettingsConfig, SettingsSelectorComponent } from \"./settings-selector.js\";\nexport { ShowImagesSelectorComponent } from \"./show-images-selector.js\";\nexport { SkillInvocationMessageComponent } from \"./skill-invocation-message.js\";\nexport { ThemeSelectorComponent } from \"./theme-selector.js\";\nexport { ThinkingSelectorComponent } from \"./thinking-selector.js\";\nexport { ToolExecutionComponent, type ToolExecutionOptions } from \"./tool-execution.js\";\nexport { TreeSelectorComponent } from \"./tree-selector.js\";\nexport { UserMessageComponent } from \"./user-message.js\";\nexport { UserMessageSelectorComponent } from \"./user-message-selector.js\";\nexport { truncateToVisualLines, type VisualTruncateResult } from \"./visual-truncate.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/index.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,EACzB,sBAAsB,GAGvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,iCAAiC,EACjC,2BAA2B,GAI5B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,iCAAiC,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAA0B,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAA2C,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AACrH,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAA+C,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAChH,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,+BAA+B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAA6B,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAsC,MAAM,qBAAqB,CAAC;AACjG,OAAO,EAAE,qBAAqB,EAA6B,MAAM,sBAAsB,CAAC","sourcesContent":["// UI Components for extensions\nexport { ArminComponent } from \"./armin.js\";\nexport { AssistantMessageComponent } from \"./assistant-message.js\";\nexport { BashExecutionComponent } from \"./bash-execution.js\";\nexport { BorderedLoader } from \"./bordered-loader.js\";\nexport {\n chatEntriesFromAgentMessages,\n LiveChatEntriesController,\n renderChatMessageEntry,\n type ChatMessageEntry,\n type ChatMessageRenderOptions,\n} from \"./chat-message-renderer.js\";\nexport {\n addChatTranscriptEntry,\n ChatTranscriptComponent,\n ScrollableChatTranscriptComponent,\n ScrollableComponentViewport,\n type ChatTranscriptEntryLike,\n type ChatTranscriptRenderer,\n type ChatTranscriptRole,\n} from \"./chat-transcript.js\";\nexport { BranchSummaryMessageComponent } from \"./branch-summary-message.js\";\nexport { CompactionSummaryMessageComponent } from \"./compaction-summary-message.js\";\nexport { CustomEditor } from \"./custom-editor.js\";\nexport { CustomMessageComponent } from \"./custom-message.js\";\nexport { DaxnutsComponent } from \"./daxnuts.js\";\nexport { type RenderDiffOptions, renderDiff } from \"./diff.js\";\nexport { DynamicBorder } from \"./dynamic-border.js\";\nexport { ExtensionEditorComponent } from \"./extension-editor.js\";\nexport { ExtensionInputComponent } from \"./extension-input.js\";\nexport { ExtensionSelectorComponent } from \"./extension-selector.js\";\nexport { FooterComponent, UsageMeterComponent } from \"./footer.js\";\nexport { keyHint, keyText, rawKeyHint } from \"./keybinding-hints.js\";\nexport { LoginDialogComponent } from \"./login-dialog.js\";\nexport { ModelSelectorComponent } from \"./model-selector.js\";\nexport { OAuthSelectorComponent } from \"./oauth-selector.js\";\nexport { type ModelsCallbacks, type ModelsConfig, ScopedModelsSelectorComponent } from \"./scoped-models-selector.js\";\nexport { SessionSelectorComponent } from \"./session-selector.js\";\nexport { type SettingsCallbacks, type SettingsConfig, SettingsSelectorComponent } from \"./settings-selector.js\";\nexport { ShowImagesSelectorComponent } from \"./show-images-selector.js\";\nexport { SkillInvocationMessageComponent } from \"./skill-invocation-message.js\";\nexport { ThemeSelectorComponent } from \"./theme-selector.js\";\nexport { ThinkingSelectorComponent } from \"./thinking-selector.js\";\nexport { ToolExecutionComponent, type ToolExecutionOptions } from \"./tool-execution.js\";\nexport { TreeSelectorComponent } from \"./tree-selector.js\";\nexport { UserMessageComponent } from \"./user-message.js\";\nexport { UserMessageSelectorComponent } from \"./user-message-selector.js\";\nexport { WorkingStatusComponent, type WorkingStatusComponentOptions } from \"./working-status.js\";\nexport { truncateToVisualLines, type VisualTruncateResult } from \"./visual-truncate.js\";\n"]}
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { type Keybinding } from "@earendil-works/pi-tui";
5
5
  export interface KeyTextFormatOptions {
6
+ /** @deprecated Key labels are always normalized for display. */
6
7
  capitalize?: boolean;
7
8
  }
8
9
  export declare function formatKeyText(key: string, options?: KeyTextFormatOptions): string;
@@ -1 +1 @@
1
- {"version":3,"file":"keybinding-hints.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/keybinding-hints.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAkB,KAAK,UAAU,EAAc,MAAM,wBAAwB,CAAC;AAGrF,MAAM,WAAW,oBAAoB;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAOD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,MAAM,CAUrF;AAOD,wBAAgB,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAE7D;AAED,wBAAgB,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAEnE","sourcesContent":["/**\n * Utilities for formatting keybinding hints in the UI.\n */\n\nimport { getKeybindings, type Keybinding, type KeyId } from \"@earendil-works/pi-tui\";\nimport { theme } from \"../theme/theme.js\";\n\nexport interface KeyTextFormatOptions {\n\tcapitalize?: boolean;\n}\n\nfunction formatKeyPart(part: string, options: KeyTextFormatOptions): string {\n\tconst displayPart = process.platform === \"darwin\" && part.toLowerCase() === \"alt\" ? \"option\" : part;\n\treturn options.capitalize ? displayPart.charAt(0).toUpperCase() + displayPart.slice(1) : displayPart;\n}\n\nexport function formatKeyText(key: string, options: KeyTextFormatOptions = {}): string {\n\treturn key\n\t\t.split(\"/\")\n\t\t.map((k) =>\n\t\t\tk\n\t\t\t\t.split(\"+\")\n\t\t\t\t.map((part) => formatKeyPart(part, options))\n\t\t\t\t.join(\"+\"),\n\t\t)\n\t\t.join(\"/\");\n}\n\nfunction formatKeys(keys: KeyId[], options: KeyTextFormatOptions = {}): string {\n\tif (keys.length === 0) return \"\";\n\treturn formatKeyText(keys.join(\"/\"), options);\n}\n\nexport function keyText(keybinding: Keybinding): string {\n\treturn formatKeys(getKeybindings().getKeys(keybinding));\n}\n\nexport function keyDisplayText(keybinding: Keybinding): string {\n\treturn formatKeys(getKeybindings().getKeys(keybinding), { capitalize: true });\n}\n\nexport function keyHint(keybinding: Keybinding, description: string): string {\n\treturn theme.fg(\"dim\", keyText(keybinding)) + theme.fg(\"muted\", ` ${description}`);\n}\n\nexport function rawKeyHint(key: string, description: string): string {\n\treturn theme.fg(\"dim\", formatKeyText(key)) + theme.fg(\"muted\", ` ${description}`);\n}\n"]}
1
+ {"version":3,"file":"keybinding-hints.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/keybinding-hints.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAkB,KAAK,UAAU,EAAc,MAAM,wBAAwB,CAAC;AAGrF,MAAM,WAAW,oBAAoB;IACpC,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AA4CD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,MAAM,CAUrF;AAOD,wBAAgB,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAE7D;AAMD,wBAAgB,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAEnE","sourcesContent":["/**\n * Utilities for formatting keybinding hints in the UI.\n */\n\nimport { getKeybindings, type Keybinding, type KeyId } from \"@earendil-works/pi-tui\";\nimport { theme } from \"../theme/theme.js\";\n\nexport interface KeyTextFormatOptions {\n\t/** @deprecated Key labels are always normalized for display. */\n\tcapitalize?: boolean;\n}\n\nconst MODIFIER_LABELS: Record<string, string> = {\n\tctrl: \"ctrl\",\n\tcontrol: \"ctrl\",\n\tcmd: \"cmd\",\n\tcommand: \"cmd\",\n\tshift: \"shift\",\n\talt: \"alt\",\n\toption: \"alt\",\n\tmeta: \"meta\",\n};\n\nconst SPECIAL_KEY_LABELS: Record<string, string> = {\n\tenter: \"enter\",\n\treturn: \"enter\",\n\tesc: \"esc\",\n\tescape: \"esc\",\n\tspace: \"space\",\n\ttab: \"tab\",\n\tbackspace: \"backspace\",\n\tdelete: \"delete\",\n\tdel: \"delete\",\n\tup: \"up\",\n\tdown: \"down\",\n\tleft: \"left\",\n\tright: \"right\",\n\thome: \"home\",\n\tend: \"end\",\n\tpageup: \"pageup\",\n\tpagedown: \"pagedown\",\n};\n\nfunction formatKeyPart(part: string, _options: KeyTextFormatOptions): string {\n\tconst lower = part.toLowerCase();\n\tconst modifier = MODIFIER_LABELS[lower];\n\tif (modifier) return modifier;\n\tconst special = SPECIAL_KEY_LABELS[lower];\n\tif (special) return special;\n\tif (/^f\\d+$/i.test(part)) return lower;\n\tif (/^[a-z]$/i.test(part)) return lower;\n\treturn part.toLowerCase();\n}\n\nexport function formatKeyText(key: string, options: KeyTextFormatOptions = {}): string {\n\treturn key\n\t\t.split(\"/\")\n\t\t.map((k) =>\n\t\t\tk\n\t\t\t\t.split(\"+\")\n\t\t\t\t.map((part) => formatKeyPart(part, options))\n\t\t\t\t.join(\"+\"),\n\t\t)\n\t\t.join(\"/\");\n}\n\nfunction formatKeys(keys: KeyId[], options: KeyTextFormatOptions = {}): string {\n\tif (keys.length === 0) return \"\";\n\treturn formatKeyText(keys.join(\"/\"), options);\n}\n\nexport function keyText(keybinding: Keybinding): string {\n\treturn formatKeys(getKeybindings().getKeys(keybinding));\n}\n\nexport function keyDisplayText(keybinding: Keybinding): string {\n\treturn formatKeys(getKeybindings().getKeys(keybinding), { capitalize: true });\n}\n\nfunction formatHintLabel(description: string): string {\n\treturn description;\n}\n\nexport function keyHint(keybinding: Keybinding, description: string): string {\n\treturn theme.fg(\"dim\", keyText(keybinding)) + theme.fg(\"muted\", ` ${formatHintLabel(description)}`);\n}\n\nexport function rawKeyHint(key: string, description: string): string {\n\treturn theme.fg(\"dim\", formatKeyText(key)) + theme.fg(\"muted\", ` ${formatHintLabel(description)}`);\n}\n"]}
@@ -3,9 +3,48 @@
3
3
  */
4
4
  import { getKeybindings } from "@earendil-works/pi-tui";
5
5
  import { theme } from "../theme/theme.js";
6
- function formatKeyPart(part, options) {
7
- const displayPart = process.platform === "darwin" && part.toLowerCase() === "alt" ? "option" : part;
8
- return options.capitalize ? displayPart.charAt(0).toUpperCase() + displayPart.slice(1) : displayPart;
6
+ const MODIFIER_LABELS = {
7
+ ctrl: "ctrl",
8
+ control: "ctrl",
9
+ cmd: "cmd",
10
+ command: "cmd",
11
+ shift: "shift",
12
+ alt: "alt",
13
+ option: "alt",
14
+ meta: "meta",
15
+ };
16
+ const SPECIAL_KEY_LABELS = {
17
+ enter: "enter",
18
+ return: "enter",
19
+ esc: "esc",
20
+ escape: "esc",
21
+ space: "space",
22
+ tab: "tab",
23
+ backspace: "backspace",
24
+ delete: "delete",
25
+ del: "delete",
26
+ up: "up",
27
+ down: "down",
28
+ left: "left",
29
+ right: "right",
30
+ home: "home",
31
+ end: "end",
32
+ pageup: "pageup",
33
+ pagedown: "pagedown",
34
+ };
35
+ function formatKeyPart(part, _options) {
36
+ const lower = part.toLowerCase();
37
+ const modifier = MODIFIER_LABELS[lower];
38
+ if (modifier)
39
+ return modifier;
40
+ const special = SPECIAL_KEY_LABELS[lower];
41
+ if (special)
42
+ return special;
43
+ if (/^f\d+$/i.test(part))
44
+ return lower;
45
+ if (/^[a-z]$/i.test(part))
46
+ return lower;
47
+ return part.toLowerCase();
9
48
  }
10
49
  export function formatKeyText(key, options = {}) {
11
50
  return key
@@ -27,10 +66,13 @@ export function keyText(keybinding) {
27
66
  export function keyDisplayText(keybinding) {
28
67
  return formatKeys(getKeybindings().getKeys(keybinding), { capitalize: true });
29
68
  }
69
+ function formatHintLabel(description) {
70
+ return description;
71
+ }
30
72
  export function keyHint(keybinding, description) {
31
- return theme.fg("dim", keyText(keybinding)) + theme.fg("muted", ` ${description}`);
73
+ return theme.fg("dim", keyText(keybinding)) + theme.fg("muted", ` ${formatHintLabel(description)}`);
32
74
  }
33
75
  export function rawKeyHint(key, description) {
34
- return theme.fg("dim", formatKeyText(key)) + theme.fg("muted", ` ${description}`);
76
+ return theme.fg("dim", formatKeyText(key)) + theme.fg("muted", ` ${formatHintLabel(description)}`);
35
77
  }
36
78
  //# sourceMappingURL=keybinding-hints.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keybinding-hints.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/keybinding-hints.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAA+B,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAM1C,SAAS,aAAa,CAAC,IAAY,EAAE,OAA6B;IACjE,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IACpG,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;AACtG,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,OAAO,GAAyB,EAAE;IAC5E,OAAO,GAAG;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACV,CAAC;SACC,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;SAC3C,IAAI,CAAC,GAAG,CAAC,CACX;SACA,IAAI,CAAC,GAAG,CAAC,CAAC;AACb,CAAC;AAED,SAAS,UAAU,CAAC,IAAa,EAAE,OAAO,GAAyB,EAAE;IACpE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,UAAsB;IAC7C,OAAO,UAAU,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,UAAsB;IACpD,OAAO,UAAU,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,UAAsB,EAAE,WAAmB;IAClE,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,WAAW,EAAE,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,WAAmB;IAC1D,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,WAAW,EAAE,CAAC,CAAC;AACnF,CAAC","sourcesContent":["/**\n * Utilities for formatting keybinding hints in the UI.\n */\n\nimport { getKeybindings, type Keybinding, type KeyId } from \"@earendil-works/pi-tui\";\nimport { theme } from \"../theme/theme.js\";\n\nexport interface KeyTextFormatOptions {\n\tcapitalize?: boolean;\n}\n\nfunction formatKeyPart(part: string, options: KeyTextFormatOptions): string {\n\tconst displayPart = process.platform === \"darwin\" && part.toLowerCase() === \"alt\" ? \"option\" : part;\n\treturn options.capitalize ? displayPart.charAt(0).toUpperCase() + displayPart.slice(1) : displayPart;\n}\n\nexport function formatKeyText(key: string, options: KeyTextFormatOptions = {}): string {\n\treturn key\n\t\t.split(\"/\")\n\t\t.map((k) =>\n\t\t\tk\n\t\t\t\t.split(\"+\")\n\t\t\t\t.map((part) => formatKeyPart(part, options))\n\t\t\t\t.join(\"+\"),\n\t\t)\n\t\t.join(\"/\");\n}\n\nfunction formatKeys(keys: KeyId[], options: KeyTextFormatOptions = {}): string {\n\tif (keys.length === 0) return \"\";\n\treturn formatKeyText(keys.join(\"/\"), options);\n}\n\nexport function keyText(keybinding: Keybinding): string {\n\treturn formatKeys(getKeybindings().getKeys(keybinding));\n}\n\nexport function keyDisplayText(keybinding: Keybinding): string {\n\treturn formatKeys(getKeybindings().getKeys(keybinding), { capitalize: true });\n}\n\nexport function keyHint(keybinding: Keybinding, description: string): string {\n\treturn theme.fg(\"dim\", keyText(keybinding)) + theme.fg(\"muted\", ` ${description}`);\n}\n\nexport function rawKeyHint(key: string, description: string): string {\n\treturn theme.fg(\"dim\", formatKeyText(key)) + theme.fg(\"muted\", ` ${description}`);\n}\n"]}
1
+ {"version":3,"file":"keybinding-hints.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/keybinding-hints.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Utilities for formatting keybinding hints in the UI.\n */\n\nimport { getKeybindings, type Keybinding, type KeyId } from \"@earendil-works/pi-tui\";\nimport { theme } from \"../theme/theme.js\";\n\nexport interface KeyTextFormatOptions {\n\t/** @deprecated Key labels are always normalized for display. */\n\tcapitalize?: boolean;\n}\n\nconst MODIFIER_LABELS: Record<string, string> = {\n\tctrl: \"ctrl\",\n\tcontrol: \"ctrl\",\n\tcmd: \"cmd\",\n\tcommand: \"cmd\",\n\tshift: \"shift\",\n\talt: \"alt\",\n\toption: \"alt\",\n\tmeta: \"meta\",\n};\n\nconst SPECIAL_KEY_LABELS: Record<string, string> = {\n\tenter: \"enter\",\n\treturn: \"enter\",\n\tesc: \"esc\",\n\tescape: \"esc\",\n\tspace: \"space\",\n\ttab: \"tab\",\n\tbackspace: \"backspace\",\n\tdelete: \"delete\",\n\tdel: \"delete\",\n\tup: \"up\",\n\tdown: \"down\",\n\tleft: \"left\",\n\tright: \"right\",\n\thome: \"home\",\n\tend: \"end\",\n\tpageup: \"pageup\",\n\tpagedown: \"pagedown\",\n};\n\nfunction formatKeyPart(part: string, _options: KeyTextFormatOptions): string {\n\tconst lower = part.toLowerCase();\n\tconst modifier = MODIFIER_LABELS[lower];\n\tif (modifier) return modifier;\n\tconst special = SPECIAL_KEY_LABELS[lower];\n\tif (special) return special;\n\tif (/^f\\d+$/i.test(part)) return lower;\n\tif (/^[a-z]$/i.test(part)) return lower;\n\treturn part.toLowerCase();\n}\n\nexport function formatKeyText(key: string, options: KeyTextFormatOptions = {}): string {\n\treturn key\n\t\t.split(\"/\")\n\t\t.map((k) =>\n\t\t\tk\n\t\t\t\t.split(\"+\")\n\t\t\t\t.map((part) => formatKeyPart(part, options))\n\t\t\t\t.join(\"+\"),\n\t\t)\n\t\t.join(\"/\");\n}\n\nfunction formatKeys(keys: KeyId[], options: KeyTextFormatOptions = {}): string {\n\tif (keys.length === 0) return \"\";\n\treturn formatKeyText(keys.join(\"/\"), options);\n}\n\nexport function keyText(keybinding: Keybinding): string {\n\treturn formatKeys(getKeybindings().getKeys(keybinding));\n}\n\nexport function keyDisplayText(keybinding: Keybinding): string {\n\treturn formatKeys(getKeybindings().getKeys(keybinding), { capitalize: true });\n}\n\nfunction formatHintLabel(description: string): string {\n\treturn description;\n}\n\nexport function keyHint(keybinding: Keybinding, description: string): string {\n\treturn theme.fg(\"dim\", keyText(keybinding)) + theme.fg(\"muted\", ` ${formatHintLabel(description)}`);\n}\n\nexport function rawKeyHint(key: string, description: string): string {\n\treturn theme.fg(\"dim\", formatKeyText(key)) + theme.fg(\"muted\", ` ${formatHintLabel(description)}`);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"login-dialog.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/login-dialog.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAuC,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAMlH;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,SAAU,YAAW,SAAS;IAqBtE,OAAO,CAAC,UAAU;IApBnB,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,aAAa,CAAC,CAA0B;IAChD,OAAO,CAAC,aAAa,CAAC,CAAyB;IAG/C,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YACC,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,MAAM,EACV,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,EAChE,oBAAoB,CAAC,EAAE,MAAM,EAC7B,aAAa,CAAC,EAAE,MAAM,EAkCtB;IAED,IAAI,MAAM,IAAI,WAAW,CAExB;IAED,OAAO,CAAC,MAAM;IAUd;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAwBjD;IAED;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW/C;IAED;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBjE;IAED;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAS9B;IAED;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAKjC;IAED;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGlC;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAU9B;CACD","sourcesContent":["import { getOAuthProviders } from \"@earendil-works/pi-ai/oauth\";\nimport { Container, type Focusable, getKeybindings, Input, Spacer, Text, type TUI } from \"@earendil-works/pi-tui\";\nimport { execFile } from \"child_process\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyHint } from \"./keybinding-hints.js\";\n\n/**\n * Login dialog component - replaces editor during OAuth login flow\n */\nexport class LoginDialogComponent extends Container implements Focusable {\n\tprivate contentContainer: Container;\n\tprivate input: Input;\n\tprivate tui: TUI;\n\tprivate abortController = new AbortController();\n\tprivate inputResolver?: (value: string) => void;\n\tprivate inputRejecter?: (error: Error) => void;\n\n\t// Focusable implementation - propagate to input for IME cursor positioning\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.input.focused = value;\n\t}\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tproviderId: string,\n\t\tprivate onComplete: (success: boolean, message?: string) => void,\n\t\tproviderNameOverride?: string,\n\t\ttitleOverride?: string,\n\t) {\n\t\tsuper();\n\t\tthis.tui = tui;\n\n\t\tconst providerInfo = getOAuthProviders().find((p) => p.id === providerId);\n\t\tconst providerName = providerNameOverride || providerInfo?.name || providerId;\n\t\tconst title = titleOverride ?? `Login to ${providerName}`;\n\n\t\t// Top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0));\n\n\t\t// Dynamic content area\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\t// Input (always present, used when needed)\n\t\tthis.input = new Input();\n\t\tthis.input.onSubmit = () => {\n\t\t\tif (this.inputResolver) {\n\t\t\t\tthis.inputResolver(this.input.getValue());\n\t\t\t\tthis.inputResolver = undefined;\n\t\t\t\tthis.inputRejecter = undefined;\n\t\t\t}\n\t\t};\n\t\tthis.input.onEscape = () => {\n\t\t\tthis.cancel();\n\t\t};\n\n\t\t// Bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tget signal(): AbortSignal {\n\t\treturn this.abortController.signal;\n\t}\n\n\tprivate cancel(): void {\n\t\tthis.abortController.abort();\n\t\tif (this.inputRejecter) {\n\t\t\tthis.inputRejecter(new Error(\"Login cancelled\"));\n\t\t\tthis.inputResolver = undefined;\n\t\t\tthis.inputRejecter = undefined;\n\t\t}\n\t\tthis.onComplete(false, \"Login cancelled\");\n\t}\n\n\t/**\n\t * Called by onAuth callback - show URL and optional instructions\n\t */\n\tshowAuth(url: string, instructions?: string): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tconst linkedUrl = `\\x1b]8;;${url}\\x07${url}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"accent\", linkedUrl), 1, 0));\n\n\t\tconst clickHint = process.platform === \"darwin\" ? \"Cmd+click to open\" : \"Ctrl+click to open\";\n\t\tconst hyperlink = `\\x1b]8;;${url}\\x07${clickHint}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", hyperlink), 1, 0));\n\n\t\tif (instructions) {\n\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"warning\", instructions), 1, 0));\n\t\t}\n\n\t\t// Try to open browser without shell interpolation.\n\t\tconst openCommand = process.platform === \"darwin\"\n\t\t\t? { command: \"open\", args: [url] }\n\t\t\t: process.platform === \"win32\"\n\t\t\t\t? { command: \"rundll32\", args: [\"url.dll,FileProtocolHandler\", url] }\n\t\t\t\t: { command: \"xdg-open\", args: [url] };\n\t\texecFile(openCommand.command, openCommand.args, () => {});\n\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Show input for manual code/URL entry (for callback server providers)\n\t */\n\tshowManualInput(prompt: string): Promise<string> {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", prompt), 1, 0));\n\t\tthis.contentContainer.addChild(this.input);\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to cancel\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.inputResolver = resolve;\n\t\t\tthis.inputRejecter = reject;\n\t\t});\n\t}\n\n\t/**\n\t * Called by onPrompt callback - show prompt and wait for input\n\t * Note: Does NOT clear content, appends to existing (preserves URL from showAuth)\n\t */\n\tshowPrompt(message: string, placeholder?: string): Promise<string> {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"text\", message), 1, 0));\n\t\tif (placeholder) {\n\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", `e.g., ${placeholder}`), 1, 0));\n\t\t}\n\t\tthis.contentContainer.addChild(this.input);\n\t\tthis.contentContainer.addChild(\n\t\t\tnew Text(\n\t\t\t\t`(${keyHint(\"tui.select.cancel\", \"to cancel,\")} ${keyHint(\"tui.select.confirm\", \"to submit\")})`,\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\n\t\tthis.input.setValue(\"\");\n\t\tthis.tui.requestRender();\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.inputResolver = resolve;\n\t\t\tthis.inputRejecter = reject;\n\t\t});\n\t}\n\n\t/**\n\t * Show informational text without prompting for input.\n\t */\n\tshowInfo(lines: string[]): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tfor (const line of lines) {\n\t\t\tthis.contentContainer.addChild(new Text(line, 1, 0));\n\t\t}\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to close\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Show waiting message (for polling flows like GitHub Copilot)\n\t */\n\tshowWaiting(message: string): void {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", message), 1, 0));\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to cancel\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Called by onProgress callback\n\t */\n\tshowProgress(message: string): void {\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", message), 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.cancel();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to input\n\t\tthis.input.handleInput(data);\n\t}\n}\n"]}
1
+ {"version":3,"file":"login-dialog.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/login-dialog.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAuC,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAMlH;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,SAAU,YAAW,SAAS;IAqBtE,OAAO,CAAC,UAAU;IApBnB,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,aAAa,CAAC,CAA0B;IAChD,OAAO,CAAC,aAAa,CAAC,CAAyB;IAG/C,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YACC,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,MAAM,EACV,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,EAChE,oBAAoB,CAAC,EAAE,MAAM,EAC7B,aAAa,CAAC,EAAE,MAAM,EAkCtB;IAED,IAAI,MAAM,IAAI,WAAW,CAExB;IAED,OAAO,CAAC,MAAM;IAUd;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAwBjD;IAED;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW/C;IAED;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBjE;IAED;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAS9B;IAED;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAKjC;IAED;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGlC;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAU9B;CACD","sourcesContent":["import { getOAuthProviders } from \"@earendil-works/pi-ai/oauth\";\nimport { Container, type Focusable, getKeybindings, Input, Spacer, Text, type TUI } from \"@earendil-works/pi-tui\";\nimport { execFile } from \"child_process\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyHint } from \"./keybinding-hints.js\";\n\n/**\n * Login dialog component - replaces editor during OAuth login flow\n */\nexport class LoginDialogComponent extends Container implements Focusable {\n\tprivate contentContainer: Container;\n\tprivate input: Input;\n\tprivate tui: TUI;\n\tprivate abortController = new AbortController();\n\tprivate inputResolver?: (value: string) => void;\n\tprivate inputRejecter?: (error: Error) => void;\n\n\t// Focusable implementation - propagate to input for IME cursor positioning\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.input.focused = value;\n\t}\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tproviderId: string,\n\t\tprivate onComplete: (success: boolean, message?: string) => void,\n\t\tproviderNameOverride?: string,\n\t\ttitleOverride?: string,\n\t) {\n\t\tsuper();\n\t\tthis.tui = tui;\n\n\t\tconst providerInfo = getOAuthProviders().find((p) => p.id === providerId);\n\t\tconst providerName = providerNameOverride || providerInfo?.name || providerId;\n\t\tconst title = titleOverride ?? `Login to ${providerName}`;\n\n\t\t// Top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0));\n\n\t\t// Dynamic content area\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\t// Input (always present, used when needed)\n\t\tthis.input = new Input();\n\t\tthis.input.onSubmit = () => {\n\t\t\tif (this.inputResolver) {\n\t\t\t\tthis.inputResolver(this.input.getValue());\n\t\t\t\tthis.inputResolver = undefined;\n\t\t\t\tthis.inputRejecter = undefined;\n\t\t\t}\n\t\t};\n\t\tthis.input.onEscape = () => {\n\t\t\tthis.cancel();\n\t\t};\n\n\t\t// Bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tget signal(): AbortSignal {\n\t\treturn this.abortController.signal;\n\t}\n\n\tprivate cancel(): void {\n\t\tthis.abortController.abort();\n\t\tif (this.inputRejecter) {\n\t\t\tthis.inputRejecter(new Error(\"Login cancelled\"));\n\t\t\tthis.inputResolver = undefined;\n\t\t\tthis.inputRejecter = undefined;\n\t\t}\n\t\tthis.onComplete(false, \"Login cancelled\");\n\t}\n\n\t/**\n\t * Called by onAuth callback - show URL and optional instructions\n\t */\n\tshowAuth(url: string, instructions?: string): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tconst linkedUrl = `\\x1b]8;;${url}\\x07${url}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"accent\", linkedUrl), 1, 0));\n\n\t\tconst clickHint = process.platform === \"darwin\" ? \"cmd+click open\" : \"ctrl+click open\";\n\t\tconst hyperlink = `\\x1b]8;;${url}\\x07${clickHint}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", hyperlink), 1, 0));\n\n\t\tif (instructions) {\n\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"warning\", instructions), 1, 0));\n\t\t}\n\n\t\t// Try to open browser without shell interpolation.\n\t\tconst openCommand = process.platform === \"darwin\"\n\t\t\t? { command: \"open\", args: [url] }\n\t\t\t: process.platform === \"win32\"\n\t\t\t\t? { command: \"rundll32\", args: [\"url.dll,FileProtocolHandler\", url] }\n\t\t\t\t: { command: \"xdg-open\", args: [url] };\n\t\texecFile(openCommand.command, openCommand.args, () => {});\n\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Show input for manual code/URL entry (for callback server providers)\n\t */\n\tshowManualInput(prompt: string): Promise<string> {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", prompt), 1, 0));\n\t\tthis.contentContainer.addChild(this.input);\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"Cancel\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.inputResolver = resolve;\n\t\t\tthis.inputRejecter = reject;\n\t\t});\n\t}\n\n\t/**\n\t * Called by onPrompt callback - show prompt and wait for input\n\t * Note: Does NOT clear content, appends to existing (preserves URL from showAuth)\n\t */\n\tshowPrompt(message: string, placeholder?: string): Promise<string> {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"text\", message), 1, 0));\n\t\tif (placeholder) {\n\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", `e.g., ${placeholder}`), 1, 0));\n\t\t}\n\t\tthis.contentContainer.addChild(this.input);\n\t\tthis.contentContainer.addChild(\n\t\t\tnew Text(\n\t\t\t\t`(${keyHint(\"tui.select.cancel\", \"Cancel,\")} ${keyHint(\"tui.select.confirm\", \"Submit\")})`,\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\n\t\tthis.input.setValue(\"\");\n\t\tthis.tui.requestRender();\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.inputResolver = resolve;\n\t\t\tthis.inputRejecter = reject;\n\t\t});\n\t}\n\n\t/**\n\t * Show informational text without prompting for input.\n\t */\n\tshowInfo(lines: string[]): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tfor (const line of lines) {\n\t\t\tthis.contentContainer.addChild(new Text(line, 1, 0));\n\t\t}\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"Close\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Show waiting message (for polling flows like GitHub Copilot)\n\t */\n\tshowWaiting(message: string): void {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", message), 1, 0));\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"Cancel\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Called by onProgress callback\n\t */\n\tshowProgress(message: string): void {\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", message), 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.cancel();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to input\n\t\tthis.input.handleInput(data);\n\t}\n}\n"]}
@@ -67,7 +67,7 @@ export class LoginDialogComponent extends Container {
67
67
  this.contentContainer.addChild(new Spacer(1));
68
68
  const linkedUrl = `\x1b]8;;${url}\x07${url}\x1b]8;;\x07`;
69
69
  this.contentContainer.addChild(new Text(theme.fg("accent", linkedUrl), 1, 0));
70
- const clickHint = process.platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open";
70
+ const clickHint = process.platform === "darwin" ? "cmd+click open" : "ctrl+click open";
71
71
  const hyperlink = `\x1b]8;;${url}\x07${clickHint}\x1b]8;;\x07`;
72
72
  this.contentContainer.addChild(new Text(theme.fg("dim", hyperlink), 1, 0));
73
73
  if (instructions) {
@@ -90,7 +90,7 @@ export class LoginDialogComponent extends Container {
90
90
  this.contentContainer.addChild(new Spacer(1));
91
91
  this.contentContainer.addChild(new Text(theme.fg("dim", prompt), 1, 0));
92
92
  this.contentContainer.addChild(this.input);
93
- this.contentContainer.addChild(new Text(`(${keyHint("tui.select.cancel", "to cancel")})`, 1, 0));
93
+ this.contentContainer.addChild(new Text(`(${keyHint("tui.select.cancel", "Cancel")})`, 1, 0));
94
94
  this.tui.requestRender();
95
95
  return new Promise((resolve, reject) => {
96
96
  this.inputResolver = resolve;
@@ -108,7 +108,7 @@ export class LoginDialogComponent extends Container {
108
108
  this.contentContainer.addChild(new Text(theme.fg("dim", `e.g., ${placeholder}`), 1, 0));
109
109
  }
110
110
  this.contentContainer.addChild(this.input);
111
- this.contentContainer.addChild(new Text(`(${keyHint("tui.select.cancel", "to cancel,")} ${keyHint("tui.select.confirm", "to submit")})`, 1, 0));
111
+ this.contentContainer.addChild(new Text(`(${keyHint("tui.select.cancel", "Cancel,")} ${keyHint("tui.select.confirm", "Submit")})`, 1, 0));
112
112
  this.input.setValue("");
113
113
  this.tui.requestRender();
114
114
  return new Promise((resolve, reject) => {
@@ -126,7 +126,7 @@ export class LoginDialogComponent extends Container {
126
126
  this.contentContainer.addChild(new Text(line, 1, 0));
127
127
  }
128
128
  this.contentContainer.addChild(new Spacer(1));
129
- this.contentContainer.addChild(new Text(`(${keyHint("tui.select.cancel", "to close")})`, 1, 0));
129
+ this.contentContainer.addChild(new Text(`(${keyHint("tui.select.cancel", "Close")})`, 1, 0));
130
130
  this.tui.requestRender();
131
131
  }
132
132
  /**
@@ -135,7 +135,7 @@ export class LoginDialogComponent extends Container {
135
135
  showWaiting(message) {
136
136
  this.contentContainer.addChild(new Spacer(1));
137
137
  this.contentContainer.addChild(new Text(theme.fg("dim", message), 1, 0));
138
- this.contentContainer.addChild(new Text(`(${keyHint("tui.select.cancel", "to cancel")})`, 1, 0));
138
+ this.contentContainer.addChild(new Text(`(${keyHint("tui.select.cancel", "Cancel")})`, 1, 0));
139
139
  this.tui.requestRender();
140
140
  }
141
141
  /**