pi-usereq 0.49.0 → 0.51.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 (940) hide show
  1. package/README.md +1 -1
  2. package/package.json +5 -1
  3. package/scripts/lib/extension-debug-harness.ts +443 -0
  4. package/scripts/lib/recording-extension-api.ts +855 -0
  5. package/scripts/lib/sdk-smoke.ts +503 -0
  6. package/src/core/extension-status.ts +13 -4
  7. package/src/index.ts +105 -71
  8. package/.g.conf +0 -16
  9. package/.github/workflows/release-npm.yml +0 -134
  10. package/.gitignore +0 -274
  11. package/CHANGELOG.md +0 -706
  12. package/TODO.md +0 -20
  13. package/docs/pi.dev/agent-document-manifest.json +0 -6399
  14. package/docs/pi.dev/coding-agent-docs/compaction.md +0 -394
  15. package/docs/pi.dev/coding-agent-docs/custom-provider.md +0 -637
  16. package/docs/pi.dev/coding-agent-docs/development.md +0 -71
  17. package/docs/pi.dev/coding-agent-docs/extensions.md +0 -2536
  18. package/docs/pi.dev/coding-agent-docs/images/doom-extension.png +0 -0
  19. package/docs/pi.dev/coding-agent-docs/images/exy.png +0 -0
  20. package/docs/pi.dev/coding-agent-docs/images/interactive-mode.png +0 -0
  21. package/docs/pi.dev/coding-agent-docs/images/tree-view.png +0 -0
  22. package/docs/pi.dev/coding-agent-docs/json.md +0 -82
  23. package/docs/pi.dev/coding-agent-docs/keybindings.md +0 -197
  24. package/docs/pi.dev/coding-agent-docs/models.md +0 -395
  25. package/docs/pi.dev/coding-agent-docs/packages.md +0 -218
  26. package/docs/pi.dev/coding-agent-docs/prompt-templates.md +0 -88
  27. package/docs/pi.dev/coding-agent-docs/providers.md +0 -196
  28. package/docs/pi.dev/coding-agent-docs/rpc.md +0 -1407
  29. package/docs/pi.dev/coding-agent-docs/sdk.md +0 -1149
  30. package/docs/pi.dev/coding-agent-docs/session.md +0 -412
  31. package/docs/pi.dev/coding-agent-docs/settings.md +0 -248
  32. package/docs/pi.dev/coding-agent-docs/shell-aliases.md +0 -13
  33. package/docs/pi.dev/coding-agent-docs/skills.md +0 -232
  34. package/docs/pi.dev/coding-agent-docs/terminal-setup.md +0 -106
  35. package/docs/pi.dev/coding-agent-docs/termux.md +0 -127
  36. package/docs/pi.dev/coding-agent-docs/themes.md +0 -295
  37. package/docs/pi.dev/coding-agent-docs/tmux.md +0 -61
  38. package/docs/pi.dev/coding-agent-docs/tree.md +0 -233
  39. package/docs/pi.dev/coding-agent-docs/tui.md +0 -918
  40. package/docs/pi.dev/coding-agent-docs/windows.md +0 -17
  41. package/docs/pi.dev/mom-docs/artifacts-server.md +0 -475
  42. package/docs/pi.dev/mom-docs/events.md +0 -307
  43. package/docs/pi.dev/mom-docs/new.md +0 -970
  44. package/docs/pi.dev/mom-docs/sandbox.md +0 -153
  45. package/docs/pi.dev/mom-docs/slack-bot-minimal-guide.md +0 -399
  46. package/docs/pi.dev/mom-docs/v86.md +0 -319
  47. package/docs/pi.dev/pods-docs/gml-4.5.md +0 -189
  48. package/docs/pi.dev/pods-docs/gpt-oss.md +0 -233
  49. package/docs/pi.dev/pods-docs/implementation-plan.md +0 -183
  50. package/docs/pi.dev/pods-docs/kimi-k2.md +0 -197
  51. package/docs/pi.dev/pods-docs/models.md +0 -116
  52. package/docs/pi.dev/pods-docs/plan.md +0 -166
  53. package/docs/pi.dev/pods-docs/qwen3-coder.md +0 -132
  54. package/images/flowchart-bw.png +0 -0
  55. package/images/flowchart-bw.svg +0 -102
  56. package/images/flowchart.md +0 -100
  57. package/images/flowchart.png +0 -0
  58. package/images/flowchart.svg +0 -3
  59. package/pi-usereq/docs/REFERENCES.md +0 -5713
  60. package/pi-usereq/docs/REQUIREMENTS.md +0 -781
  61. package/pi-usereq/docs/WORKFLOW.md +0 -1341
  62. package/pi.dev-src/pi-mono/agent/src/agent-loop.ts +0 -683
  63. package/pi.dev-src/pi-mono/agent/src/agent.ts +0 -543
  64. package/pi.dev-src/pi-mono/agent/src/index.ts +0 -8
  65. package/pi.dev-src/pi-mono/agent/src/proxy.ts +0 -367
  66. package/pi.dev-src/pi-mono/agent/src/types.ts +0 -364
  67. package/pi.dev-src/pi-mono/agent/test/agent-loop.test.ts +0 -1181
  68. package/pi.dev-src/pi-mono/agent/test/agent.test.ts +0 -463
  69. package/pi.dev-src/pi-mono/agent/test/e2e.test.ts +0 -404
  70. package/pi.dev-src/pi-mono/agent/test/utils/calculate.ts +0 -32
  71. package/pi.dev-src/pi-mono/agent/test/utils/get-current-time.ts +0 -46
  72. package/pi.dev-src/pi-mono/agent/vitest.config.ts +0 -9
  73. package/pi.dev-src/pi-mono/ai/bedrock-provider.d.ts +0 -1
  74. package/pi.dev-src/pi-mono/ai/bedrock-provider.js +0 -1
  75. package/pi.dev-src/pi-mono/ai/scripts/generate-models.ts +0 -1635
  76. package/pi.dev-src/pi-mono/ai/scripts/generate-test-image.ts +0 -34
  77. package/pi.dev-src/pi-mono/ai/src/api-registry.ts +0 -98
  78. package/pi.dev-src/pi-mono/ai/src/bedrock-provider.ts +0 -6
  79. package/pi.dev-src/pi-mono/ai/src/cli.ts +0 -133
  80. package/pi.dev-src/pi-mono/ai/src/env-api-keys.ts +0 -134
  81. package/pi.dev-src/pi-mono/ai/src/index.ts +0 -35
  82. package/pi.dev-src/pi-mono/ai/src/models.generated.ts +0 -14928
  83. package/pi.dev-src/pi-mono/ai/src/models.ts +0 -82
  84. package/pi.dev-src/pi-mono/ai/src/oauth.ts +0 -1
  85. package/pi.dev-src/pi-mono/ai/src/providers/amazon-bedrock.ts +0 -929
  86. package/pi.dev-src/pi-mono/ai/src/providers/anthropic.ts +0 -1125
  87. package/pi.dev-src/pi-mono/ai/src/providers/azure-openai-responses.ts +0 -253
  88. package/pi.dev-src/pi-mono/ai/src/providers/faux.ts +0 -499
  89. package/pi.dev-src/pi-mono/ai/src/providers/github-copilot-headers.ts +0 -37
  90. package/pi.dev-src/pi-mono/ai/src/providers/google-gemini-cli.ts +0 -996
  91. package/pi.dev-src/pi-mono/ai/src/providers/google-shared.ts +0 -354
  92. package/pi.dev-src/pi-mono/ai/src/providers/google-vertex.ts +0 -543
  93. package/pi.dev-src/pi-mono/ai/src/providers/google.ts +0 -500
  94. package/pi.dev-src/pi-mono/ai/src/providers/mistral.ts +0 -629
  95. package/pi.dev-src/pi-mono/ai/src/providers/openai-codex-responses.ts +0 -984
  96. package/pi.dev-src/pi-mono/ai/src/providers/openai-completions.ts +0 -1063
  97. package/pi.dev-src/pi-mono/ai/src/providers/openai-responses-shared.ts +0 -539
  98. package/pi.dev-src/pi-mono/ai/src/providers/openai-responses.ts +0 -264
  99. package/pi.dev-src/pi-mono/ai/src/providers/register-builtins.ts +0 -433
  100. package/pi.dev-src/pi-mono/ai/src/providers/simple-options.ts +0 -47
  101. package/pi.dev-src/pi-mono/ai/src/providers/transform-messages.ts +0 -220
  102. package/pi.dev-src/pi-mono/ai/src/stream.ts +0 -59
  103. package/pi.dev-src/pi-mono/ai/src/types.ts +0 -417
  104. package/pi.dev-src/pi-mono/ai/src/utils/event-stream.ts +0 -87
  105. package/pi.dev-src/pi-mono/ai/src/utils/hash.ts +0 -13
  106. package/pi.dev-src/pi-mono/ai/src/utils/headers.ts +0 -7
  107. package/pi.dev-src/pi-mono/ai/src/utils/json-parse.ts +0 -124
  108. package/pi.dev-src/pi-mono/ai/src/utils/overflow.ts +0 -138
  109. package/pi.dev-src/pi-mono/ai/src/utils/sanitize-unicode.ts +0 -25
  110. package/pi.dev-src/pi-mono/ai/src/utils/typebox-helpers.ts +0 -24
  111. package/pi.dev-src/pi-mono/ai/src/utils/validation.ts +0 -324
  112. package/pi.dev-src/pi-mono/ai/test/abort.test.ts +0 -260
  113. package/pi.dev-src/pi-mono/ai/test/anthropic-oauth.test.ts +0 -99
  114. package/pi.dev-src/pi-mono/ai/test/anthropic-opus-4-7-smoke.test.ts +0 -72
  115. package/pi.dev-src/pi-mono/ai/test/anthropic-sse-parsing.test.ts +0 -113
  116. package/pi.dev-src/pi-mono/ai/test/anthropic-thinking-disable.test.ts +0 -152
  117. package/pi.dev-src/pi-mono/ai/test/anthropic-tool-name-normalization.test.ts +0 -205
  118. package/pi.dev-src/pi-mono/ai/test/azure-utils.ts +0 -28
  119. package/pi.dev-src/pi-mono/ai/test/bedrock-endpoint-resolution.test.ts +0 -131
  120. package/pi.dev-src/pi-mono/ai/test/bedrock-models.test.ts +0 -66
  121. package/pi.dev-src/pi-mono/ai/test/bedrock-thinking-payload.test.ts +0 -107
  122. package/pi.dev-src/pi-mono/ai/test/bedrock-utils.ts +0 -18
  123. package/pi.dev-src/pi-mono/ai/test/cache-retention.test.ts +0 -335
  124. package/pi.dev-src/pi-mono/ai/test/context-overflow.test.ts +0 -714
  125. package/pi.dev-src/pi-mono/ai/test/cross-provider-handoff.test.ts +0 -458
  126. package/pi.dev-src/pi-mono/ai/test/data/red-circle.png +0 -0
  127. package/pi.dev-src/pi-mono/ai/test/empty.test.ts +0 -742
  128. package/pi.dev-src/pi-mono/ai/test/faux-provider.test.ts +0 -597
  129. package/pi.dev-src/pi-mono/ai/test/fireworks-models.test.ts +0 -49
  130. package/pi.dev-src/pi-mono/ai/test/github-copilot-anthropic.test.ts +0 -108
  131. package/pi.dev-src/pi-mono/ai/test/github-copilot-oauth.test.ts +0 -196
  132. package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-claude-thinking-header.test.ts +0 -103
  133. package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-empty-stream.test.ts +0 -108
  134. package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-retry-delay.test.ts +0 -53
  135. package/pi.dev-src/pi-mono/ai/test/google-shared-convert-tools.test.ts +0 -187
  136. package/pi.dev-src/pi-mono/ai/test/google-shared-gemini3-unsigned-tool-call.test.ts +0 -172
  137. package/pi.dev-src/pi-mono/ai/test/google-shared-image-tool-result-routing.test.ts +0 -124
  138. package/pi.dev-src/pi-mono/ai/test/google-thinking-disable.test.ts +0 -183
  139. package/pi.dev-src/pi-mono/ai/test/google-thinking-signature.test.ts +0 -38
  140. package/pi.dev-src/pi-mono/ai/test/google-tool-call-missing-args.test.ts +0 -105
  141. package/pi.dev-src/pi-mono/ai/test/google-vertex-api-key-resolution.test.ts +0 -144
  142. package/pi.dev-src/pi-mono/ai/test/image-tool-result.test.ts +0 -481
  143. package/pi.dev-src/pi-mono/ai/test/interleaved-thinking.test.ts +0 -145
  144. package/pi.dev-src/pi-mono/ai/test/lazy-module-load.test.ts +0 -102
  145. package/pi.dev-src/pi-mono/ai/test/mistral-reasoning-mode.test.ts +0 -66
  146. package/pi.dev-src/pi-mono/ai/test/mistral-tool-schema.test.ts +0 -61
  147. package/pi.dev-src/pi-mono/ai/test/oauth.ts +0 -89
  148. package/pi.dev-src/pi-mono/ai/test/openai-codex-cache-affinity-e2e.test.ts +0 -36
  149. package/pi.dev-src/pi-mono/ai/test/openai-codex-stream.test.ts +0 -686
  150. package/pi.dev-src/pi-mono/ai/test/openai-completions-cache-control-format.test.ts +0 -189
  151. package/pi.dev-src/pi-mono/ai/test/openai-completions-prompt-cache.test.ts +0 -198
  152. package/pi.dev-src/pi-mono/ai/test/openai-completions-thinking-as-text.test.ts +0 -214
  153. package/pi.dev-src/pi-mono/ai/test/openai-completions-tool-choice.test.ts +0 -560
  154. package/pi.dev-src/pi-mono/ai/test/openai-completions-tool-result-images.test.ts +0 -101
  155. package/pi.dev-src/pi-mono/ai/test/openai-responses-cache-affinity-e2e.test.ts +0 -32
  156. package/pi.dev-src/pi-mono/ai/test/openai-responses-copilot-provider.test.ts +0 -128
  157. package/pi.dev-src/pi-mono/ai/test/openai-responses-foreign-toolcall-id.test.ts +0 -66
  158. package/pi.dev-src/pi-mono/ai/test/openai-responses-partial-json-cleanup.test.ts +0 -101
  159. package/pi.dev-src/pi-mono/ai/test/openai-responses-reasoning-replay-e2e.test.ts +0 -292
  160. package/pi.dev-src/pi-mono/ai/test/openai-responses-tool-result-images.test.ts +0 -200
  161. package/pi.dev-src/pi-mono/ai/test/openrouter-cache-write-repro.test.ts +0 -78
  162. package/pi.dev-src/pi-mono/ai/test/overflow.test.ts +0 -64
  163. package/pi.dev-src/pi-mono/ai/test/responseid.test.ts +0 -145
  164. package/pi.dev-src/pi-mono/ai/test/stream.test.ts +0 -1405
  165. package/pi.dev-src/pi-mono/ai/test/supports-xhigh.test.ts +0 -34
  166. package/pi.dev-src/pi-mono/ai/test/tokens.test.ts +0 -304
  167. package/pi.dev-src/pi-mono/ai/test/tool-call-id-normalization.test.ts +0 -291
  168. package/pi.dev-src/pi-mono/ai/test/tool-call-without-result.test.ts +0 -313
  169. package/pi.dev-src/pi-mono/ai/test/total-tokens.test.ts +0 -695
  170. package/pi.dev-src/pi-mono/ai/test/transform-messages-copilot-openai-to-anthropic.test.ts +0 -191
  171. package/pi.dev-src/pi-mono/ai/test/unicode-surrogate.test.ts +0 -754
  172. package/pi.dev-src/pi-mono/ai/test/validation.test.ts +0 -116
  173. package/pi.dev-src/pi-mono/ai/test/xhigh.test.ts +0 -71
  174. package/pi.dev-src/pi-mono/ai/test/zen.test.ts +0 -25
  175. package/pi.dev-src/pi-mono/ai/vitest.config.ts +0 -9
  176. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/antigravity-image-gen.ts +0 -418
  177. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/auto-commit-on-exit.ts +0 -49
  178. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/bash-spawn-hook.ts +0 -30
  179. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/bookmark.ts +0 -50
  180. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/built-in-tool-renderer.ts +0 -249
  181. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/claude-rules.ts +0 -86
  182. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/commands.ts +0 -72
  183. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/confirm-destructive.ts +0 -59
  184. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-compaction.ts +0 -127
  185. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-footer.ts +0 -64
  186. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-header.ts +0 -73
  187. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-anthropic/index.ts +0 -604
  188. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +0 -24
  189. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-gitlab-duo/index.ts +0 -349
  190. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-gitlab-duo/test.ts +0 -82
  191. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-qwen-cli/index.ts +0 -345
  192. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dirty-repo-guard.ts +0 -56
  193. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom/build.sh +0 -152
  194. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +0 -72
  195. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-component.ts +0 -132
  196. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-engine.ts +0 -173
  197. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-keys.ts +0 -104
  198. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/index.ts +0 -74
  199. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/wad-finder.ts +0 -51
  200. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/SKILL.md +0 -8
  201. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/dynamic.json +0 -79
  202. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/dynamic.md +0 -5
  203. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/index.ts +0 -15
  204. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-tools.ts +0 -74
  205. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/event-bus.ts +0 -43
  206. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/file-trigger.ts +0 -41
  207. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/git-checkpoint.ts +0 -53
  208. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/github-issue-autocomplete.ts +0 -185
  209. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/handoff.ts +0 -153
  210. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/hello.ts +0 -26
  211. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/hidden-thinking-label.ts +0 -53
  212. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/inline-bash.ts +0 -94
  213. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/input-transform.ts +0 -43
  214. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/interactive-shell.ts +0 -196
  215. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/mac-system-theme.ts +0 -47
  216. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/message-renderer.ts +0 -59
  217. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/minimal-mode.ts +0 -426
  218. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/modal-editor.ts +0 -85
  219. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/model-status.ts +0 -31
  220. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/notify.ts +0 -55
  221. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/overlay-qa-tests.ts +0 -1348
  222. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/overlay-test.ts +0 -150
  223. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/permission-gate.ts +0 -34
  224. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/pirate.ts +0 -47
  225. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/plan-mode/index.ts +0 -340
  226. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/plan-mode/utils.ts +0 -168
  227. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/preset.ts +0 -430
  228. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/prompt-customizer.ts +0 -97
  229. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/protected-paths.ts +0 -30
  230. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/provider-payload.ts +0 -18
  231. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/qna.ts +0 -122
  232. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/question.ts +0 -264
  233. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/questionnaire.ts +0 -427
  234. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/rainbow-editor.ts +0 -88
  235. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/reload-runtime.ts +0 -37
  236. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/rpc-demo.ts +0 -118
  237. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/sandbox/index.ts +0 -321
  238. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/sandbox/package-lock.json +0 -92
  239. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/send-user-message.ts +0 -97
  240. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/session-name.ts +0 -27
  241. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/shutdown-command.ts +0 -63
  242. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/snake.ts +0 -343
  243. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/space-invaders.ts +0 -560
  244. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/ssh.ts +0 -220
  245. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/status-line.ts +0 -32
  246. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/structured-output.ts +0 -65
  247. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/planner.md +0 -37
  248. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/reviewer.md +0 -35
  249. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/scout.md +0 -50
  250. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/worker.md +0 -24
  251. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents.ts +0 -126
  252. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/index.ts +0 -987
  253. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
  254. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/implement.md +0 -10
  255. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
  256. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/summarize.ts +0 -206
  257. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/system-prompt-header.ts +0 -17
  258. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tic-tac-toe.ts +0 -1008
  259. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/timed-confirm.ts +0 -70
  260. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/titlebar-spinner.ts +0 -58
  261. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/todo.ts +0 -297
  262. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tool-override.ts +0 -144
  263. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tools.ts +0 -141
  264. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/trigger-compact.ts +0 -50
  265. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/truncated-tool.ts +0 -195
  266. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/widget-placement.ts +0 -9
  267. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/with-deps/index.ts +0 -32
  268. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/with-deps/package-lock.json +0 -31
  269. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/working-indicator.ts +0 -123
  270. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/working-message-test.ts +0 -25
  271. package/pi.dev-src/pi-mono/coding-agent/examples/rpc-extension-ui.ts +0 -632
  272. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/01-minimal.ts +0 -22
  273. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/02-custom-model.ts +0 -49
  274. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/03-custom-prompt.ts +0 -62
  275. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/04-skills.ts +0 -55
  276. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/05-tools.ts +0 -44
  277. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/06-extensions.ts +0 -90
  278. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/07-context-files.ts +0 -42
  279. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/08-prompt-templates.ts +0 -51
  280. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/09-api-keys-and-oauth.ts +0 -48
  281. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/10-settings.ts +0 -53
  282. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/11-sessions.ts +0 -48
  283. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/12-full-control.ts +0 -73
  284. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/13-session-runtime.ts +0 -67
  285. package/pi.dev-src/pi-mono/coding-agent/scripts/migrate-sessions.sh +0 -93
  286. package/pi.dev-src/pi-mono/coding-agent/src/bun/cli.ts +0 -6
  287. package/pi.dev-src/pi-mono/coding-agent/src/bun/register-bedrock.ts +0 -4
  288. package/pi.dev-src/pi-mono/coding-agent/src/cli/args.ts +0 -335
  289. package/pi.dev-src/pi-mono/coding-agent/src/cli/config-selector.ts +0 -52
  290. package/pi.dev-src/pi-mono/coding-agent/src/cli/file-processor.ts +0 -100
  291. package/pi.dev-src/pi-mono/coding-agent/src/cli/initial-message.ts +0 -43
  292. package/pi.dev-src/pi-mono/coding-agent/src/cli/list-models.ts +0 -110
  293. package/pi.dev-src/pi-mono/coding-agent/src/cli/session-picker.ts +0 -52
  294. package/pi.dev-src/pi-mono/coding-agent/src/cli.ts +0 -17
  295. package/pi.dev-src/pi-mono/coding-agent/src/config.ts +0 -261
  296. package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session-runtime.ts +0 -394
  297. package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session-services.ts +0 -196
  298. package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session.ts +0 -3096
  299. package/pi.dev-src/pi-mono/coding-agent/src/core/auth-storage.ts +0 -493
  300. package/pi.dev-src/pi-mono/coding-agent/src/core/bash-executor.ts +0 -154
  301. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/branch-summarization.ts +0 -355
  302. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/compaction.ts +0 -839
  303. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/index.ts +0 -7
  304. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/utils.ts +0 -170
  305. package/pi.dev-src/pi-mono/coding-agent/src/core/defaults.ts +0 -3
  306. package/pi.dev-src/pi-mono/coding-agent/src/core/diagnostics.ts +0 -15
  307. package/pi.dev-src/pi-mono/coding-agent/src/core/event-bus.ts +0 -33
  308. package/pi.dev-src/pi-mono/coding-agent/src/core/exec.ts +0 -107
  309. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/ansi-to-html.ts +0 -258
  310. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/index.ts +0 -314
  311. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.css +0 -1017
  312. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.html +0 -55
  313. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.js +0 -1770
  314. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/tool-renderer.ts +0 -158
  315. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/vendor/highlight.min.js +0 -1213
  316. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/vendor/marked.min.js +0 -6
  317. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/index.ts +0 -171
  318. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/loader.ts +0 -606
  319. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/runner.ts +0 -1019
  320. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/types.ts +0 -1542
  321. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/wrapper.ts +0 -30
  322. package/pi.dev-src/pi-mono/coding-agent/src/core/footer-data-provider.ts +0 -354
  323. package/pi.dev-src/pi-mono/coding-agent/src/core/index.ts +0 -77
  324. package/pi.dev-src/pi-mono/coding-agent/src/core/keybindings.ts +0 -370
  325. package/pi.dev-src/pi-mono/coding-agent/src/core/messages.ts +0 -195
  326. package/pi.dev-src/pi-mono/coding-agent/src/core/model-registry.ts +0 -858
  327. package/pi.dev-src/pi-mono/coding-agent/src/core/model-resolver.ts +0 -629
  328. package/pi.dev-src/pi-mono/coding-agent/src/core/output-guard.ts +0 -74
  329. package/pi.dev-src/pi-mono/coding-agent/src/core/package-manager.ts +0 -2408
  330. package/pi.dev-src/pi-mono/coding-agent/src/core/prompt-templates.ts +0 -296
  331. package/pi.dev-src/pi-mono/coding-agent/src/core/resolve-config-value.ts +0 -142
  332. package/pi.dev-src/pi-mono/coding-agent/src/core/resource-loader.ts +0 -917
  333. package/pi.dev-src/pi-mono/coding-agent/src/core/sdk.ts +0 -380
  334. package/pi.dev-src/pi-mono/coding-agent/src/core/session-cwd.ts +0 -59
  335. package/pi.dev-src/pi-mono/coding-agent/src/core/session-manager.ts +0 -1425
  336. package/pi.dev-src/pi-mono/coding-agent/src/core/settings-manager.ts +0 -999
  337. package/pi.dev-src/pi-mono/coding-agent/src/core/skills.ts +0 -508
  338. package/pi.dev-src/pi-mono/coding-agent/src/core/slash-commands.ts +0 -39
  339. package/pi.dev-src/pi-mono/coding-agent/src/core/source-info.ts +0 -40
  340. package/pi.dev-src/pi-mono/coding-agent/src/core/system-prompt.ts +0 -172
  341. package/pi.dev-src/pi-mono/coding-agent/src/core/telemetry.ts +0 -13
  342. package/pi.dev-src/pi-mono/coding-agent/src/core/timings.ts +0 -31
  343. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/bash.ts +0 -448
  344. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/edit-diff.ts +0 -445
  345. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/edit.ts +0 -487
  346. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/file-mutation-queue.ts +0 -39
  347. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/find.ts +0 -370
  348. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/grep.ts +0 -384
  349. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/index.ts +0 -196
  350. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/ls.ts +0 -229
  351. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/path-utils.ts +0 -94
  352. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/read.ts +0 -273
  353. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/render-utils.ts +0 -64
  354. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/tool-definition-wrapper.ts +0 -45
  355. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/truncate.ts +0 -265
  356. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/write.ts +0 -281
  357. package/pi.dev-src/pi-mono/coding-agent/src/index.ts +0 -352
  358. package/pi.dev-src/pi-mono/coding-agent/src/main.ts +0 -731
  359. package/pi.dev-src/pi-mono/coding-agent/src/migrations.ts +0 -314
  360. package/pi.dev-src/pi-mono/coding-agent/src/modes/index.ts +0 -9
  361. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/assets/clankolas.png +0 -0
  362. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/armin.ts +0 -382
  363. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/assistant-message.ts +0 -147
  364. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/bash-execution.ts +0 -218
  365. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/bordered-loader.ts +0 -68
  366. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/branch-summary-message.ts +0 -58
  367. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/compaction-summary-message.ts +0 -59
  368. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/config-selector.ts +0 -592
  369. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/countdown-timer.ts +0 -38
  370. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/custom-editor.ts +0 -80
  371. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/custom-message.ts +0 -99
  372. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/daxnuts.ts +0 -164
  373. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/diff.ts +0 -147
  374. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/dynamic-border.ts +0 -25
  375. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/earendil-announcement.ts +0 -53
  376. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-editor.ts +0 -147
  377. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-input.ts +0 -87
  378. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-selector.ts +0 -107
  379. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/footer.ts +0 -220
  380. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/index.ts +0 -32
  381. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/keybinding-hints.ts +0 -24
  382. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/login-dialog.ts +0 -178
  383. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/model-selector.ts +0 -338
  384. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/oauth-selector.ts +0 -121
  385. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/scoped-models-selector.ts +0 -355
  386. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/session-selector-search.ts +0 -194
  387. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/session-selector.ts +0 -1026
  388. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/settings-selector.ts +0 -456
  389. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/show-images-selector.ts +0 -50
  390. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/skill-invocation-message.ts +0 -55
  391. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/theme-selector.ts +0 -67
  392. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/thinking-selector.ts +0 -74
  393. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/tool-execution.ts +0 -352
  394. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/tree-selector.ts +0 -1246
  395. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/user-message-selector.ts +0 -155
  396. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/user-message.ts +0 -35
  397. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/visual-truncate.ts +0 -50
  398. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/interactive-mode.ts +0 -5155
  399. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/dark.json +0 -85
  400. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/light.json +0 -84
  401. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/theme-schema.json +0 -335
  402. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/theme.ts +0 -1141
  403. package/pi.dev-src/pi-mono/coding-agent/src/modes/print-mode.ts +0 -158
  404. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/jsonl.ts +0 -58
  405. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-client.ts +0 -515
  406. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-mode.ts +0 -745
  407. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-types.ts +0 -264
  408. package/pi.dev-src/pi-mono/coding-agent/src/package-manager-cli.ts +0 -270
  409. package/pi.dev-src/pi-mono/coding-agent/src/utils/changelog.ts +0 -99
  410. package/pi.dev-src/pi-mono/coding-agent/src/utils/child-process.ts +0 -86
  411. package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard-image.ts +0 -300
  412. package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard-native.ts +0 -22
  413. package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard.ts +0 -81
  414. package/pi.dev-src/pi-mono/coding-agent/src/utils/exif-orientation.ts +0 -183
  415. package/pi.dev-src/pi-mono/coding-agent/src/utils/frontmatter.ts +0 -39
  416. package/pi.dev-src/pi-mono/coding-agent/src/utils/fs-watch.ts +0 -30
  417. package/pi.dev-src/pi-mono/coding-agent/src/utils/git.ts +0 -192
  418. package/pi.dev-src/pi-mono/coding-agent/src/utils/image-convert.ts +0 -41
  419. package/pi.dev-src/pi-mono/coding-agent/src/utils/image-resize.ts +0 -176
  420. package/pi.dev-src/pi-mono/coding-agent/src/utils/mime.ts +0 -30
  421. package/pi.dev-src/pi-mono/coding-agent/src/utils/paths.ts +0 -20
  422. package/pi.dev-src/pi-mono/coding-agent/src/utils/photon.ts +0 -139
  423. package/pi.dev-src/pi-mono/coding-agent/src/utils/shell.ts +0 -210
  424. package/pi.dev-src/pi-mono/coding-agent/src/utils/sleep.ts +0 -18
  425. package/pi.dev-src/pi-mono/coding-agent/src/utils/tools-manager.ts +0 -287
  426. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-auto-compaction-queue.test.ts +0 -437
  427. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-branching.test.ts +0 -156
  428. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-compaction.test.ts +0 -208
  429. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-concurrent.test.ts +0 -618
  430. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-dynamic-provider.test.ts +0 -117
  431. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-dynamic-tools.test.ts +0 -182
  432. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-retry.test.ts +0 -318
  433. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-runtime-events.test.ts +0 -208
  434. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-stats.test.ts +0 -143
  435. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-tree-navigation.test.ts +0 -323
  436. package/pi.dev-src/pi-mono/coding-agent/test/args.test.ts +0 -328
  437. package/pi.dev-src/pi-mono/coding-agent/test/assistant-message.test.ts +0 -57
  438. package/pi.dev-src/pi-mono/coding-agent/test/auth-storage.test.ts +0 -462
  439. package/pi.dev-src/pi-mono/coding-agent/test/bash-close-hang-windows.test.ts +0 -126
  440. package/pi.dev-src/pi-mono/coding-agent/test/bash-execution-width.test.ts +0 -80
  441. package/pi.dev-src/pi-mono/coding-agent/test/block-images.test.ts +0 -120
  442. package/pi.dev-src/pi-mono/coding-agent/test/clipboard-image-bmp-conversion.test.ts +0 -88
  443. package/pi.dev-src/pi-mono/coding-agent/test/clipboard-image.test.ts +0 -136
  444. package/pi.dev-src/pi-mono/coding-agent/test/compaction-extensions-example.test.ts +0 -66
  445. package/pi.dev-src/pi-mono/coding-agent/test/compaction-extensions.test.ts +0 -415
  446. package/pi.dev-src/pi-mono/coding-agent/test/compaction-serialization.test.ts +0 -79
  447. package/pi.dev-src/pi-mono/coding-agent/test/compaction-summary-reasoning.test.ts +0 -118
  448. package/pi.dev-src/pi-mono/coding-agent/test/compaction-thinking-model.test.ts +0 -223
  449. package/pi.dev-src/pi-mono/coding-agent/test/compaction.test.ts +0 -550
  450. package/pi.dev-src/pi-mono/coding-agent/test/config.test.ts +0 -30
  451. package/pi.dev-src/pi-mono/coding-agent/test/edit-tool-legacy-input.test.ts +0 -116
  452. package/pi.dev-src/pi-mono/coding-agent/test/edit-tool-no-full-redraw.test.ts +0 -235
  453. package/pi.dev-src/pi-mono/coding-agent/test/export-html-whitespace.test.ts +0 -12
  454. package/pi.dev-src/pi-mono/coding-agent/test/export-html-xss.test.ts +0 -28
  455. package/pi.dev-src/pi-mono/coding-agent/test/extensions-discovery.test.ts +0 -463
  456. package/pi.dev-src/pi-mono/coding-agent/test/extensions-input-event.test.ts +0 -112
  457. package/pi.dev-src/pi-mono/coding-agent/test/extensions-runner.test.ts +0 -805
  458. package/pi.dev-src/pi-mono/coding-agent/test/file-mutation-queue.test.ts +0 -163
  459. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/assistant-message-with-thinking-code.json +0 -33
  460. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/before-compaction.jsonl +0 -1003
  461. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/empty-agent/.gitkeep +0 -0
  462. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/empty-cwd/.gitkeep +0 -0
  463. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/large-session.jsonl +0 -1019
  464. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/consecutive-hyphens/SKILL.md +0 -8
  465. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/disable-model-invocation/SKILL.md +0 -9
  466. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/invalid-name-chars/SKILL.md +0 -8
  467. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/invalid-yaml/SKILL.md +0 -8
  468. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/long-name/SKILL.md +0 -8
  469. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/missing-description/SKILL.md +0 -7
  470. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/multiline-description/SKILL.md +0 -11
  471. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/name-mismatch/SKILL.md +0 -8
  472. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/nested/child-skill/SKILL.md +0 -8
  473. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/no-frontmatter/SKILL.md +0 -3
  474. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/root-skill-preferred/SKILL.md +0 -3
  475. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/root-skill-preferred/nested-child/SKILL.md +0 -3
  476. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/unknown-field/SKILL.md +0 -10
  477. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/valid-skill/SKILL.md +0 -8
  478. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills-collision/first/calendar/SKILL.md +0 -8
  479. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills-collision/second/calendar/SKILL.md +0 -8
  480. package/pi.dev-src/pi-mono/coding-agent/test/footer-data-provider.test.ts +0 -265
  481. package/pi.dev-src/pi-mono/coding-agent/test/footer-width.test.ts +0 -115
  482. package/pi.dev-src/pi-mono/coding-agent/test/frontmatter.test.ts +0 -60
  483. package/pi.dev-src/pi-mono/coding-agent/test/git-ssh-url.test.ts +0 -78
  484. package/pi.dev-src/pi-mono/coding-agent/test/git-update.test.ts +0 -411
  485. package/pi.dev-src/pi-mono/coding-agent/test/image-processing.test.ts +0 -145
  486. package/pi.dev-src/pi-mono/coding-agent/test/image-resize-callers.test.ts +0 -53
  487. package/pi.dev-src/pi-mono/coding-agent/test/initial-message.test.ts +0 -48
  488. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-anthropic-warning.test.ts +0 -73
  489. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-clone-command.test.ts +0 -77
  490. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-compaction.test.ts +0 -57
  491. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-import-command.test.ts +0 -144
  492. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-status.test.ts +0 -635
  493. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-suspend.test.ts +0 -149
  494. package/pi.dev-src/pi-mono/coding-agent/test/keybindings-migration.test.ts +0 -88
  495. package/pi.dev-src/pi-mono/coding-agent/test/model-registry.test.ts +0 -1209
  496. package/pi.dev-src/pi-mono/coding-agent/test/model-resolver.test.ts +0 -436
  497. package/pi.dev-src/pi-mono/coding-agent/test/package-command-paths.test.ts +0 -144
  498. package/pi.dev-src/pi-mono/coding-agent/test/package-manager-ssh.test.ts +0 -97
  499. package/pi.dev-src/pi-mono/coding-agent/test/package-manager.test.ts +0 -1795
  500. package/pi.dev-src/pi-mono/coding-agent/test/path-utils.test.ts +0 -162
  501. package/pi.dev-src/pi-mono/coding-agent/test/plan-mode-utils.test.ts +0 -261
  502. package/pi.dev-src/pi-mono/coding-agent/test/print-mode.test.ts +0 -140
  503. package/pi.dev-src/pi-mono/coding-agent/test/prompt-templates.test.ts +0 -515
  504. package/pi.dev-src/pi-mono/coding-agent/test/resource-loader.test.ts +0 -576
  505. package/pi.dev-src/pi-mono/coding-agent/test/rpc-client-clone.test.ts +0 -29
  506. package/pi.dev-src/pi-mono/coding-agent/test/rpc-example.ts +0 -86
  507. package/pi.dev-src/pi-mono/coding-agent/test/rpc-jsonl.test.ts +0 -65
  508. package/pi.dev-src/pi-mono/coding-agent/test/rpc-prompt-response-semantics.test.ts +0 -285
  509. package/pi.dev-src/pi-mono/coding-agent/test/rpc.test.ts +0 -321
  510. package/pi.dev-src/pi-mono/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts +0 -364
  511. package/pi.dev-src/pi-mono/coding-agent/test/sdk-openrouter-attribution.test.ts +0 -181
  512. package/pi.dev-src/pi-mono/coding-agent/test/sdk-session-manager.test.ts +0 -95
  513. package/pi.dev-src/pi-mono/coding-agent/test/sdk-skills.test.ts +0 -109
  514. package/pi.dev-src/pi-mono/coding-agent/test/session-cwd.test.ts +0 -91
  515. package/pi.dev-src/pi-mono/coding-agent/test/session-info-modified-timestamp.test.ts +0 -83
  516. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/build-context.test.ts +0 -268
  517. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/custom-session-id.test.ts +0 -109
  518. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/file-operations.test.ts +0 -208
  519. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/labels.test.ts +0 -198
  520. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/migration.test.ts +0 -78
  521. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/save-entry.test.ts +0 -55
  522. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/tree-traversal.test.ts +0 -533
  523. package/pi.dev-src/pi-mono/coding-agent/test/session-selector-path-delete.test.ts +0 -315
  524. package/pi.dev-src/pi-mono/coding-agent/test/session-selector-rename.test.ts +0 -111
  525. package/pi.dev-src/pi-mono/coding-agent/test/session-selector-search.test.ts +0 -195
  526. package/pi.dev-src/pi-mono/coding-agent/test/settings-manager-bug.test.ts +0 -147
  527. package/pi.dev-src/pi-mono/coding-agent/test/settings-manager.test.ts +0 -319
  528. package/pi.dev-src/pi-mono/coding-agent/test/skills.test.ts +0 -432
  529. package/pi.dev-src/pi-mono/coding-agent/test/stdout-cleanliness.test.ts +0 -103
  530. package/pi.dev-src/pi-mono/coding-agent/test/streaming-render-debug.ts +0 -97
  531. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-bash-persistence.test.ts +0 -242
  532. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-compaction.test.ts +0 -343
  533. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-model-extension.test.ts +0 -325
  534. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-prompt.test.ts +0 -323
  535. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-queue.test.ts +0 -422
  536. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-retry-events.test.ts +0 -358
  537. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-runtime.test.ts +0 -553
  538. package/pi.dev-src/pi-mono/coding-agent/test/suite/harness.ts +0 -208
  539. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2023-queued-slash-command-followup.test.ts +0 -80
  540. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2753-reload-stale-resource-settings.test.ts +0 -100
  541. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2781-skill-collision-precedence.test.ts +0 -120
  542. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2791-fswatch-error-crash.test.ts +0 -106
  543. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2835-tools-allowlist-filters-extension-tools.test.ts +0 -94
  544. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2860-replaced-session-context.test.ts +0 -274
  545. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3217-scoped-model-order.test.ts +0 -104
  546. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3302-find-path-glob.test.ts +0 -72
  547. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3303-find-nested-gitignore.test.ts +0 -83
  548. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3317-network-connection-lost-retry.test.ts +0 -33
  549. package/pi.dev-src/pi-mono/coding-agent/test/system-prompt.test.ts +0 -102
  550. package/pi.dev-src/pi-mono/coding-agent/test/test-harness.test.ts +0 -321
  551. package/pi.dev-src/pi-mono/coding-agent/test/test-harness.ts +0 -446
  552. package/pi.dev-src/pi-mono/coding-agent/test/test-theme-colors.ts +0 -246
  553. package/pi.dev-src/pi-mono/coding-agent/test/theme-export.test.ts +0 -104
  554. package/pi.dev-src/pi-mono/coding-agent/test/tool-execution-component.test.ts +0 -331
  555. package/pi.dev-src/pi-mono/coding-agent/test/tools.test.ts +0 -889
  556. package/pi.dev-src/pi-mono/coding-agent/test/tree-selector.test.ts +0 -655
  557. package/pi.dev-src/pi-mono/coding-agent/test/trigger-compact-extension.test.ts +0 -57
  558. package/pi.dev-src/pi-mono/coding-agent/test/truncate-to-width.test.ts +0 -81
  559. package/pi.dev-src/pi-mono/coding-agent/test/user-message.test.ts +0 -25
  560. package/pi.dev-src/pi-mono/coding-agent/test/utilities.ts +0 -316
  561. package/pi.dev-src/pi-mono/coding-agent/tsconfig.examples.json +0 -16
  562. package/pi.dev-src/pi-mono/coding-agent/vitest.config.ts +0 -26
  563. package/pi.dev-src/pi-mono/mom/dev.sh +0 -30
  564. package/pi.dev-src/pi-mono/mom/docker.sh +0 -95
  565. package/pi.dev-src/pi-mono/mom/scripts/migrate-timestamps.ts +0 -121
  566. package/pi.dev-src/pi-mono/mom/src/agent.ts +0 -882
  567. package/pi.dev-src/pi-mono/mom/src/context.ts +0 -180
  568. package/pi.dev-src/pi-mono/mom/src/download.ts +0 -117
  569. package/pi.dev-src/pi-mono/mom/src/events.ts +0 -442
  570. package/pi.dev-src/pi-mono/mom/src/fs-watch.ts +0 -30
  571. package/pi.dev-src/pi-mono/mom/src/log.ts +0 -271
  572. package/pi.dev-src/pi-mono/mom/src/main.ts +0 -367
  573. package/pi.dev-src/pi-mono/mom/src/sandbox.ts +0 -221
  574. package/pi.dev-src/pi-mono/mom/src/slack.ts +0 -623
  575. package/pi.dev-src/pi-mono/mom/src/store.ts +0 -234
  576. package/pi.dev-src/pi-mono/mom/src/tools/attach.ts +0 -47
  577. package/pi.dev-src/pi-mono/mom/src/tools/bash.ts +0 -97
  578. package/pi.dev-src/pi-mono/mom/src/tools/edit.ts +0 -165
  579. package/pi.dev-src/pi-mono/mom/src/tools/index.ts +0 -19
  580. package/pi.dev-src/pi-mono/mom/src/tools/read.ts +0 -159
  581. package/pi.dev-src/pi-mono/mom/src/tools/truncate.ts +0 -236
  582. package/pi.dev-src/pi-mono/mom/src/tools/write.ts +0 -45
  583. package/pi.dev-src/pi-mono/mom/test/events.test.ts +0 -50
  584. package/pi.dev-src/pi-mono/pods/scripts/model_run.sh +0 -83
  585. package/pi.dev-src/pi-mono/pods/scripts/pod_setup.sh +0 -336
  586. package/pi.dev-src/pi-mono/pods/src/cli.ts +0 -360
  587. package/pi.dev-src/pi-mono/pods/src/commands/models.ts +0 -753
  588. package/pi.dev-src/pi-mono/pods/src/commands/pods.ts +0 -205
  589. package/pi.dev-src/pi-mono/pods/src/commands/prompt.ts +0 -84
  590. package/pi.dev-src/pi-mono/pods/src/config.ts +0 -80
  591. package/pi.dev-src/pi-mono/pods/src/index.ts +0 -2
  592. package/pi.dev-src/pi-mono/pods/src/model-configs.ts +0 -111
  593. package/pi.dev-src/pi-mono/pods/src/models.json +0 -295
  594. package/pi.dev-src/pi-mono/pods/src/ssh.ts +0 -151
  595. package/pi.dev-src/pi-mono/pods/src/types.ts +0 -27
  596. package/pi.dev-src/pi-mono/tui/src/autocomplete.ts +0 -783
  597. package/pi.dev-src/pi-mono/tui/src/components/box.ts +0 -137
  598. package/pi.dev-src/pi-mono/tui/src/components/cancellable-loader.ts +0 -40
  599. package/pi.dev-src/pi-mono/tui/src/components/editor.ts +0 -2280
  600. package/pi.dev-src/pi-mono/tui/src/components/image.ts +0 -104
  601. package/pi.dev-src/pi-mono/tui/src/components/input.ts +0 -503
  602. package/pi.dev-src/pi-mono/tui/src/components/loader.ts +0 -86
  603. package/pi.dev-src/pi-mono/tui/src/components/markdown.ts +0 -852
  604. package/pi.dev-src/pi-mono/tui/src/components/select-list.ts +0 -229
  605. package/pi.dev-src/pi-mono/tui/src/components/settings-list.ts +0 -250
  606. package/pi.dev-src/pi-mono/tui/src/components/spacer.ts +0 -28
  607. package/pi.dev-src/pi-mono/tui/src/components/text.ts +0 -106
  608. package/pi.dev-src/pi-mono/tui/src/components/truncated-text.ts +0 -65
  609. package/pi.dev-src/pi-mono/tui/src/editor-component.ts +0 -74
  610. package/pi.dev-src/pi-mono/tui/src/fuzzy.ts +0 -133
  611. package/pi.dev-src/pi-mono/tui/src/index.ts +0 -106
  612. package/pi.dev-src/pi-mono/tui/src/keybindings.ts +0 -244
  613. package/pi.dev-src/pi-mono/tui/src/keys.ts +0 -1400
  614. package/pi.dev-src/pi-mono/tui/src/kill-ring.ts +0 -46
  615. package/pi.dev-src/pi-mono/tui/src/stdin-buffer.ts +0 -386
  616. package/pi.dev-src/pi-mono/tui/src/terminal-image.ts +0 -414
  617. package/pi.dev-src/pi-mono/tui/src/terminal.ts +0 -373
  618. package/pi.dev-src/pi-mono/tui/src/tui.ts +0 -1243
  619. package/pi.dev-src/pi-mono/tui/src/undo-stack.ts +0 -28
  620. package/pi.dev-src/pi-mono/tui/src/utils.ts +0 -1085
  621. package/pi.dev-src/pi-mono/tui/test/autocomplete.test.ts +0 -542
  622. package/pi.dev-src/pi-mono/tui/test/bug-regression-isimageline-startswith-bug.test.ts +0 -237
  623. package/pi.dev-src/pi-mono/tui/test/chat-simple.ts +0 -129
  624. package/pi.dev-src/pi-mono/tui/test/editor.test.ts +0 -3812
  625. package/pi.dev-src/pi-mono/tui/test/fuzzy.test.ts +0 -98
  626. package/pi.dev-src/pi-mono/tui/test/image-test.ts +0 -56
  627. package/pi.dev-src/pi-mono/tui/test/input.test.ts +0 -580
  628. package/pi.dev-src/pi-mono/tui/test/key-tester.ts +0 -105
  629. package/pi.dev-src/pi-mono/tui/test/keybindings.test.ts +0 -38
  630. package/pi.dev-src/pi-mono/tui/test/keys.test.ts +0 -614
  631. package/pi.dev-src/pi-mono/tui/test/markdown.test.ts +0 -1236
  632. package/pi.dev-src/pi-mono/tui/test/overlay-non-capturing.test.ts +0 -609
  633. package/pi.dev-src/pi-mono/tui/test/overlay-options.test.ts +0 -538
  634. package/pi.dev-src/pi-mono/tui/test/overlay-short-content.test.ts +0 -56
  635. package/pi.dev-src/pi-mono/tui/test/regression-regional-indicator-width.test.ts +0 -52
  636. package/pi.dev-src/pi-mono/tui/test/select-list.test.ts +0 -116
  637. package/pi.dev-src/pi-mono/tui/test/stdin-buffer.test.ts +0 -422
  638. package/pi.dev-src/pi-mono/tui/test/terminal-image.test.ts +0 -279
  639. package/pi.dev-src/pi-mono/tui/test/test-themes.ts +0 -38
  640. package/pi.dev-src/pi-mono/tui/test/truncate-to-width.test.ts +0 -62
  641. package/pi.dev-src/pi-mono/tui/test/truncated-text.test.ts +0 -129
  642. package/pi.dev-src/pi-mono/tui/test/tui-cell-size-input.test.ts +0 -81
  643. package/pi.dev-src/pi-mono/tui/test/tui-overlay-style-leak.test.ts +0 -72
  644. package/pi.dev-src/pi-mono/tui/test/tui-render.test.ts +0 -509
  645. package/pi.dev-src/pi-mono/tui/test/viewport-overwrite-repro.ts +0 -106
  646. package/pi.dev-src/pi-mono/tui/test/virtual-terminal.ts +0 -218
  647. package/pi.dev-src/pi-mono/tui/test/wrap-ansi.test.ts +0 -207
  648. package/pi.dev-src/pi-mono/tui/vitest.config.ts +0 -7
  649. package/pi.dev-src/pi-mono/web-ui/example/index.html +0 -13
  650. package/pi.dev-src/pi-mono/web-ui/example/src/app.css +0 -1
  651. package/pi.dev-src/pi-mono/web-ui/example/src/custom-messages.ts +0 -99
  652. package/pi.dev-src/pi-mono/web-ui/example/src/main.ts +0 -421
  653. package/pi.dev-src/pi-mono/web-ui/example/tsconfig.json +0 -23
  654. package/pi.dev-src/pi-mono/web-ui/example/vite.config.ts +0 -6
  655. package/pi.dev-src/pi-mono/web-ui/scripts/count-prompt-tokens.ts +0 -88
  656. package/pi.dev-src/pi-mono/web-ui/src/ChatPanel.ts +0 -209
  657. package/pi.dev-src/pi-mono/web-ui/src/app.css +0 -68
  658. package/pi.dev-src/pi-mono/web-ui/src/components/AgentInterface.ts +0 -403
  659. package/pi.dev-src/pi-mono/web-ui/src/components/AttachmentTile.ts +0 -107
  660. package/pi.dev-src/pi-mono/web-ui/src/components/ConsoleBlock.ts +0 -72
  661. package/pi.dev-src/pi-mono/web-ui/src/components/CustomProviderCard.ts +0 -100
  662. package/pi.dev-src/pi-mono/web-ui/src/components/ExpandableSection.ts +0 -46
  663. package/pi.dev-src/pi-mono/web-ui/src/components/Input.ts +0 -113
  664. package/pi.dev-src/pi-mono/web-ui/src/components/MessageEditor.ts +0 -405
  665. package/pi.dev-src/pi-mono/web-ui/src/components/MessageList.ts +0 -98
  666. package/pi.dev-src/pi-mono/web-ui/src/components/Messages.ts +0 -383
  667. package/pi.dev-src/pi-mono/web-ui/src/components/ProviderKeyInput.ts +0 -153
  668. package/pi.dev-src/pi-mono/web-ui/src/components/SandboxedIframe.ts +0 -626
  669. package/pi.dev-src/pi-mono/web-ui/src/components/StreamingMessageContainer.ts +0 -103
  670. package/pi.dev-src/pi-mono/web-ui/src/components/ThinkingBlock.ts +0 -43
  671. package/pi.dev-src/pi-mono/web-ui/src/components/message-renderer-registry.ts +0 -28
  672. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/ArtifactsRuntimeProvider.ts +0 -220
  673. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/AttachmentsRuntimeProvider.ts +0 -66
  674. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/ConsoleRuntimeProvider.ts +0 -186
  675. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/FileDownloadRuntimeProvider.ts +0 -110
  676. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/RuntimeMessageBridge.ts +0 -82
  677. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/RuntimeMessageRouter.ts +0 -216
  678. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/SandboxRuntimeProvider.ts +0 -52
  679. package/pi.dev-src/pi-mono/web-ui/src/dialogs/ApiKeyPromptDialog.ts +0 -75
  680. package/pi.dev-src/pi-mono/web-ui/src/dialogs/AttachmentOverlay.ts +0 -636
  681. package/pi.dev-src/pi-mono/web-ui/src/dialogs/CustomProviderDialog.ts +0 -274
  682. package/pi.dev-src/pi-mono/web-ui/src/dialogs/ModelSelector.ts +0 -370
  683. package/pi.dev-src/pi-mono/web-ui/src/dialogs/PersistentStorageDialog.ts +0 -144
  684. package/pi.dev-src/pi-mono/web-ui/src/dialogs/ProvidersModelsTab.ts +0 -212
  685. package/pi.dev-src/pi-mono/web-ui/src/dialogs/SessionListDialog.ts +0 -150
  686. package/pi.dev-src/pi-mono/web-ui/src/dialogs/SettingsDialog.ts +0 -218
  687. package/pi.dev-src/pi-mono/web-ui/src/index.ts +0 -120
  688. package/pi.dev-src/pi-mono/web-ui/src/prompts/prompts.ts +0 -282
  689. package/pi.dev-src/pi-mono/web-ui/src/storage/app-storage.ts +0 -60
  690. package/pi.dev-src/pi-mono/web-ui/src/storage/backends/indexeddb-storage-backend.ts +0 -193
  691. package/pi.dev-src/pi-mono/web-ui/src/storage/store.ts +0 -33
  692. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/custom-providers-store.ts +0 -62
  693. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/provider-keys-store.ts +0 -33
  694. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/sessions-store.ts +0 -136
  695. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/settings-store.ts +0 -34
  696. package/pi.dev-src/pi-mono/web-ui/src/storage/types.ts +0 -206
  697. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ArtifactElement.ts +0 -14
  698. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ArtifactPill.ts +0 -26
  699. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/Console.ts +0 -93
  700. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/DocxArtifact.ts +0 -213
  701. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ExcelArtifact.ts +0 -231
  702. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/GenericArtifact.ts +0 -117
  703. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/HtmlArtifact.ts +0 -195
  704. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ImageArtifact.ts +0 -116
  705. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/MarkdownArtifact.ts +0 -82
  706. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/PdfArtifact.ts +0 -201
  707. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/SvgArtifact.ts +0 -119
  708. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/TextArtifact.ts +0 -148
  709. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/artifacts-tool-renderer.ts +0 -310
  710. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/artifacts.ts +0 -713
  711. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/index.ts +0 -7
  712. package/pi.dev-src/pi-mono/web-ui/src/tools/extract-document.ts +0 -275
  713. package/pi.dev-src/pi-mono/web-ui/src/tools/index.ts +0 -46
  714. package/pi.dev-src/pi-mono/web-ui/src/tools/javascript-repl.ts +0 -293
  715. package/pi.dev-src/pi-mono/web-ui/src/tools/renderer-registry.ts +0 -130
  716. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/BashRenderer.ts +0 -52
  717. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/CalculateRenderer.ts +0 -58
  718. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/DefaultRenderer.ts +0 -103
  719. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/GetCurrentTimeRenderer.ts +0 -92
  720. package/pi.dev-src/pi-mono/web-ui/src/tools/types.ts +0 -15
  721. package/pi.dev-src/pi-mono/web-ui/src/utils/attachment-utils.ts +0 -472
  722. package/pi.dev-src/pi-mono/web-ui/src/utils/auth-token.ts +0 -22
  723. package/pi.dev-src/pi-mono/web-ui/src/utils/format.ts +0 -42
  724. package/pi.dev-src/pi-mono/web-ui/src/utils/i18n.ts +0 -653
  725. package/pi.dev-src/pi-mono/web-ui/src/utils/model-discovery.ts +0 -277
  726. package/pi.dev-src/pi-mono/web-ui/src/utils/proxy-utils.ts +0 -139
  727. package/pi.dev-src/pi-mono/web-ui/src/utils/test-sessions.ts +0 -2357
  728. package/pi.dev-src/pi-mono/web-ui/tsconfig.json +0 -7
  729. package/tests/attended-results-scenarios.ts +0 -648
  730. package/tests/attended-results.test.ts +0 -39
  731. package/tests/cli-command-option-parity.test.ts +0 -798
  732. package/tests/debug-extension-harness.test.ts +0 -466
  733. package/tests/extension-registration.test.ts +0 -6785
  734. package/tests/fixtures/fixture_c.c +0 -361
  735. package/tests/fixtures/fixture_cpp.cpp +0 -407
  736. package/tests/fixtures/fixture_csharp.cs +0 -411
  737. package/tests/fixtures/fixture_elixir.ex +0 -409
  738. package/tests/fixtures/fixture_go.go +0 -341
  739. package/tests/fixtures/fixture_haskell.hs +0 -250
  740. package/tests/fixtures/fixture_java.java +0 -430
  741. package/tests/fixtures/fixture_javascript.js +0 -383
  742. package/tests/fixtures/fixture_kotlin.kt +0 -451
  743. package/tests/fixtures/fixture_lua.lua +0 -276
  744. package/tests/fixtures/fixture_perl.pl +0 -310
  745. package/tests/fixtures/fixture_php.php +0 -433
  746. package/tests/fixtures/fixture_python.py +0 -502
  747. package/tests/fixtures/fixture_ruby.rb +0 -345
  748. package/tests/fixtures/fixture_rust.rs +0 -380
  749. package/tests/fixtures/fixture_scala.scala +0 -398
  750. package/tests/fixtures/fixture_shell.sh +0 -276
  751. package/tests/fixtures/fixture_swift.swift +0 -397
  752. package/tests/fixtures/fixture_typescript.ts +0 -434
  753. package/tests/fixtures/fixture_zig.zig +0 -295
  754. package/tests/fixtures_attended_results/project/compress-line-numbers.json +0 -5
  755. package/tests/fixtures_attended_results/project/compress.json +0 -5
  756. package/tests/fixtures_attended_results/project/enable-static-check-invalid-command.json +0 -5
  757. package/tests/fixtures_attended_results/project/enable-static-check-valid.json +0 -5
  758. package/tests/fixtures_attended_results/project/files-static-check.json +0 -5
  759. package/tests/fixtures_attended_results/project/find-line-numbers.json +0 -5
  760. package/tests/fixtures_attended_results/project/find.json +0 -5
  761. package/tests/fixtures_attended_results/project/get-base-path.json +0 -5
  762. package/tests/fixtures_attended_results/project/git-check-clean.json +0 -5
  763. package/tests/fixtures_attended_results/project/git-check-dirty.json +0 -5
  764. package/tests/fixtures_attended_results/project/git-path.json +0 -5
  765. package/tests/fixtures_attended_results/project/git-wt-create-invalid.json +0 -5
  766. package/tests/fixtures_attended_results/project/git-wt-create-valid.json +0 -5
  767. package/tests/fixtures_attended_results/project/git-wt-delete-nonexistent.json +0 -5
  768. package/tests/fixtures_attended_results/project/git-wt-delete-valid.json +0 -5
  769. package/tests/fixtures_attended_results/project/git-wt-name.json +0 -5
  770. package/tests/fixtures_attended_results/project/static-check.json +0 -5
  771. package/tests/fixtures_attended_results/project/summarize.json +0 -5
  772. package/tests/fixtures_attended_results/project/tokens.json +0 -5
  773. package/tests/fixtures_attended_results/standalone/files-compress/fixture_c.c.json +0 -5
  774. package/tests/fixtures_attended_results/standalone/files-compress/fixture_cpp.cpp.json +0 -5
  775. package/tests/fixtures_attended_results/standalone/files-compress/fixture_csharp.cs.json +0 -5
  776. package/tests/fixtures_attended_results/standalone/files-compress/fixture_elixir.ex.json +0 -5
  777. package/tests/fixtures_attended_results/standalone/files-compress/fixture_go.go.json +0 -5
  778. package/tests/fixtures_attended_results/standalone/files-compress/fixture_haskell.hs.json +0 -5
  779. package/tests/fixtures_attended_results/standalone/files-compress/fixture_java.java.json +0 -5
  780. package/tests/fixtures_attended_results/standalone/files-compress/fixture_javascript.js.json +0 -5
  781. package/tests/fixtures_attended_results/standalone/files-compress/fixture_kotlin.kt.json +0 -5
  782. package/tests/fixtures_attended_results/standalone/files-compress/fixture_lua.lua.json +0 -5
  783. package/tests/fixtures_attended_results/standalone/files-compress/fixture_perl.pl.json +0 -5
  784. package/tests/fixtures_attended_results/standalone/files-compress/fixture_php.php.json +0 -5
  785. package/tests/fixtures_attended_results/standalone/files-compress/fixture_python.py.json +0 -5
  786. package/tests/fixtures_attended_results/standalone/files-compress/fixture_ruby.rb.json +0 -5
  787. package/tests/fixtures_attended_results/standalone/files-compress/fixture_rust.rs.json +0 -5
  788. package/tests/fixtures_attended_results/standalone/files-compress/fixture_scala.scala.json +0 -5
  789. package/tests/fixtures_attended_results/standalone/files-compress/fixture_shell.sh.json +0 -5
  790. package/tests/fixtures_attended_results/standalone/files-compress/fixture_swift.swift.json +0 -5
  791. package/tests/fixtures_attended_results/standalone/files-compress/fixture_typescript.ts.json +0 -5
  792. package/tests/fixtures_attended_results/standalone/files-compress/fixture_zig.zig.json +0 -5
  793. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_c.c.json +0 -5
  794. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_cpp.cpp.json +0 -5
  795. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_csharp.cs.json +0 -5
  796. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_elixir.ex.json +0 -5
  797. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_go.go.json +0 -5
  798. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_haskell.hs.json +0 -5
  799. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_java.java.json +0 -5
  800. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_javascript.js.json +0 -5
  801. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_kotlin.kt.json +0 -5
  802. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_lua.lua.json +0 -5
  803. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_perl.pl.json +0 -5
  804. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_php.php.json +0 -5
  805. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_python.py.json +0 -5
  806. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_ruby.rb.json +0 -5
  807. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_rust.rs.json +0 -5
  808. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_scala.scala.json +0 -5
  809. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_shell.sh.json +0 -5
  810. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_swift.swift.json +0 -5
  811. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_typescript.ts.json +0 -5
  812. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_zig.zig.json +0 -5
  813. package/tests/fixtures_attended_results/standalone/files-find/fixture_c.c.json +0 -5
  814. package/tests/fixtures_attended_results/standalone/files-find/fixture_cpp.cpp.json +0 -5
  815. package/tests/fixtures_attended_results/standalone/files-find/fixture_csharp.cs.json +0 -5
  816. package/tests/fixtures_attended_results/standalone/files-find/fixture_elixir.ex.json +0 -5
  817. package/tests/fixtures_attended_results/standalone/files-find/fixture_go.go.json +0 -5
  818. package/tests/fixtures_attended_results/standalone/files-find/fixture_haskell.hs.json +0 -5
  819. package/tests/fixtures_attended_results/standalone/files-find/fixture_java.java.json +0 -5
  820. package/tests/fixtures_attended_results/standalone/files-find/fixture_javascript.js.json +0 -5
  821. package/tests/fixtures_attended_results/standalone/files-find/fixture_kotlin.kt.json +0 -5
  822. package/tests/fixtures_attended_results/standalone/files-find/fixture_lua.lua.json +0 -5
  823. package/tests/fixtures_attended_results/standalone/files-find/fixture_perl.pl.json +0 -5
  824. package/tests/fixtures_attended_results/standalone/files-find/fixture_php.php.json +0 -5
  825. package/tests/fixtures_attended_results/standalone/files-find/fixture_python.py.json +0 -5
  826. package/tests/fixtures_attended_results/standalone/files-find/fixture_ruby.rb.json +0 -5
  827. package/tests/fixtures_attended_results/standalone/files-find/fixture_rust.rs.json +0 -5
  828. package/tests/fixtures_attended_results/standalone/files-find/fixture_scala.scala.json +0 -5
  829. package/tests/fixtures_attended_results/standalone/files-find/fixture_shell.sh.json +0 -5
  830. package/tests/fixtures_attended_results/standalone/files-find/fixture_swift.swift.json +0 -5
  831. package/tests/fixtures_attended_results/standalone/files-find/fixture_typescript.ts.json +0 -5
  832. package/tests/fixtures_attended_results/standalone/files-find/fixture_zig.zig.json +0 -5
  833. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_c.c.json +0 -5
  834. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_cpp.cpp.json +0 -5
  835. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_csharp.cs.json +0 -5
  836. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_elixir.ex.json +0 -5
  837. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_go.go.json +0 -5
  838. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_haskell.hs.json +0 -5
  839. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_java.java.json +0 -5
  840. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_javascript.js.json +0 -5
  841. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_kotlin.kt.json +0 -5
  842. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_lua.lua.json +0 -5
  843. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_perl.pl.json +0 -5
  844. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_php.php.json +0 -5
  845. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_python.py.json +0 -5
  846. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_ruby.rb.json +0 -5
  847. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_rust.rs.json +0 -5
  848. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_scala.scala.json +0 -5
  849. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_shell.sh.json +0 -5
  850. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_swift.swift.json +0 -5
  851. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_typescript.ts.json +0 -5
  852. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_zig.zig.json +0 -5
  853. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_c.c.json +0 -5
  854. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_cpp.cpp.json +0 -5
  855. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_csharp.cs.json +0 -5
  856. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_elixir.ex.json +0 -5
  857. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_go.go.json +0 -5
  858. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_haskell.hs.json +0 -5
  859. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_java.java.json +0 -5
  860. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_javascript.js.json +0 -5
  861. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_kotlin.kt.json +0 -5
  862. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_lua.lua.json +0 -5
  863. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_perl.pl.json +0 -5
  864. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_php.php.json +0 -5
  865. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_python.py.json +0 -5
  866. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_ruby.rb.json +0 -5
  867. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_rust.rs.json +0 -5
  868. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_scala.scala.json +0 -5
  869. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_shell.sh.json +0 -5
  870. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_swift.swift.json +0 -5
  871. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_typescript.ts.json +0 -5
  872. package/tests/fixtures_attended_results/standalone/files-summarize/fixture_zig.zig.json +0 -5
  873. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_c.c.json +0 -5
  874. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_cpp.cpp.json +0 -5
  875. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_csharp.cs.json +0 -5
  876. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_elixir.ex.json +0 -5
  877. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_go.go.json +0 -5
  878. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_haskell.hs.json +0 -5
  879. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_java.java.json +0 -5
  880. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_javascript.js.json +0 -5
  881. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_kotlin.kt.json +0 -5
  882. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_lua.lua.json +0 -5
  883. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_perl.pl.json +0 -5
  884. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_php.php.json +0 -5
  885. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_python.py.json +0 -5
  886. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_ruby.rb.json +0 -5
  887. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_rust.rs.json +0 -5
  888. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_scala.scala.json +0 -5
  889. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_shell.sh.json +0 -5
  890. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_swift.swift.json +0 -5
  891. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_typescript.ts.json +0 -5
  892. package/tests/fixtures_attended_results/standalone/files-tokens/fixture_zig.zig.json +0 -5
  893. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_c.c.json +0 -5
  894. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_cpp.cpp.json +0 -5
  895. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_csharp.cs.json +0 -5
  896. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_elixir.ex.json +0 -5
  897. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_go.go.json +0 -5
  898. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_haskell.hs.json +0 -5
  899. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_java.java.json +0 -5
  900. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_javascript.js.json +0 -5
  901. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_kotlin.kt.json +0 -5
  902. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_lua.lua.json +0 -5
  903. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_perl.pl.json +0 -5
  904. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_php.php.json +0 -5
  905. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_python.py.json +0 -5
  906. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_ruby.rb.json +0 -5
  907. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_rust.rs.json +0 -5
  908. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_scala.scala.json +0 -5
  909. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_shell.sh.json +0 -5
  910. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_swift.swift.json +0 -5
  911. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_typescript.ts.json +0 -5
  912. package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_zig.zig.json +0 -5
  913. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_c.c.json +0 -5
  914. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_cpp.cpp.json +0 -5
  915. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_csharp.cs.json +0 -5
  916. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_elixir.ex.json +0 -5
  917. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_go.go.json +0 -5
  918. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_haskell.hs.json +0 -5
  919. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_java.java.json +0 -5
  920. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_javascript.js.json +0 -5
  921. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_kotlin.kt.json +0 -5
  922. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_lua.lua.json +0 -5
  923. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_perl.pl.json +0 -5
  924. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_php.php.json +0 -5
  925. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_python.py.json +0 -5
  926. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_ruby.rb.json +0 -5
  927. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_rust.rs.json +0 -5
  928. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_scala.scala.json +0 -5
  929. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_shell.sh.json +0 -5
  930. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_swift.swift.json +0 -5
  931. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_typescript.ts.json +0 -5
  932. package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_zig.zig.json +0 -5
  933. package/tests/helpers.ts +0 -236
  934. package/tests/oracle-project.test.ts +0 -67
  935. package/tests/oracle-standalone.test.ts +0 -68
  936. package/tests/prompt-command-summary.test.ts +0 -36
  937. package/tests/prompt-rendering.test.ts +0 -103
  938. package/tests/python_oracle_stubs/tiktoken.py +0 -76
  939. package/tests/release-workflow.test.ts +0 -195
  940. package/tests/static-check-bundled.test.ts +0 -332
@@ -1,2280 +0,0 @@
1
- import type { AutocompleteProvider, AutocompleteSuggestions } from "../autocomplete.js";
2
- import { getKeybindings } from "../keybindings.js";
3
- import { decodePrintableKey, matchesKey } from "../keys.js";
4
- import { KillRing } from "../kill-ring.js";
5
- import { type Component, CURSOR_MARKER, type Focusable, type TUI } from "../tui.js";
6
- import { UndoStack } from "../undo-stack.js";
7
- import { getSegmenter, isPunctuationChar, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils.js";
8
- import { SelectList, type SelectListLayoutOptions, type SelectListTheme } from "./select-list.js";
9
-
10
- const baseSegmenter = getSegmenter();
11
-
12
- /** Regex matching paste markers like `[paste #1 +123 lines]` or `[paste #2 1234 chars]`. */
13
- const PASTE_MARKER_REGEX = /\[paste #(\d+)( (\+\d+ lines|\d+ chars))?\]/g;
14
-
15
- /** Non-global version for single-segment testing. */
16
- const PASTE_MARKER_SINGLE = /^\[paste #(\d+)( (\+\d+ lines|\d+ chars))?\]$/;
17
-
18
- /** Check if a segment is a paste marker (i.e. was merged by segmentWithMarkers). */
19
- function isPasteMarker(segment: string): boolean {
20
- return segment.length >= 10 && PASTE_MARKER_SINGLE.test(segment);
21
- }
22
-
23
- /**
24
- * A segmenter that wraps Intl.Segmenter and merges graphemes that fall
25
- * within paste markers into single atomic segments. This makes cursor
26
- * movement, deletion, word-wrap, etc. treat paste markers as single units.
27
- *
28
- * Only markers whose numeric ID exists in `validIds` are merged.
29
- */
30
- function segmentWithMarkers(text: string, validIds: Set<number>): Iterable<Intl.SegmentData> {
31
- // Fast path: no paste markers in the text or no valid IDs.
32
- if (validIds.size === 0 || !text.includes("[paste #")) {
33
- return baseSegmenter.segment(text);
34
- }
35
-
36
- // Find all marker spans with valid IDs.
37
- const markers: Array<{ start: number; end: number }> = [];
38
- for (const m of text.matchAll(PASTE_MARKER_REGEX)) {
39
- const id = Number.parseInt(m[1]!, 10);
40
- if (!validIds.has(id)) continue;
41
- markers.push({ start: m.index, end: m.index + m[0].length });
42
- }
43
- if (markers.length === 0) {
44
- return baseSegmenter.segment(text);
45
- }
46
-
47
- // Build merged segment list.
48
- const baseSegments = baseSegmenter.segment(text);
49
- const result: Intl.SegmentData[] = [];
50
- let markerIdx = 0;
51
-
52
- for (const seg of baseSegments) {
53
- // Skip past markers that are entirely before this segment.
54
- while (markerIdx < markers.length && markers[markerIdx]!.end <= seg.index) {
55
- markerIdx++;
56
- }
57
-
58
- const marker = markerIdx < markers.length ? markers[markerIdx]! : null;
59
-
60
- if (marker && seg.index >= marker.start && seg.index < marker.end) {
61
- // This segment falls inside a marker.
62
- // If this is the first segment of the marker, emit a merged segment.
63
- if (seg.index === marker.start) {
64
- const markerText = text.slice(marker.start, marker.end);
65
- result.push({
66
- segment: markerText,
67
- index: marker.start,
68
- input: text,
69
- });
70
- }
71
- // Otherwise skip (already merged into the first segment).
72
- } else {
73
- result.push(seg);
74
- }
75
- }
76
-
77
- return result;
78
- }
79
-
80
- /**
81
- * Represents a chunk of text for word-wrap layout.
82
- * Tracks both the text content and its position in the original line.
83
- */
84
- export interface TextChunk {
85
- text: string;
86
- startIndex: number;
87
- endIndex: number;
88
- }
89
-
90
- /**
91
- * Split a line into word-wrapped chunks.
92
- * Wraps at word boundaries when possible, falling back to character-level
93
- * wrapping for words longer than the available width.
94
- *
95
- * @param line - The text line to wrap
96
- * @param maxWidth - Maximum visible width per chunk
97
- * @param preSegmented - Optional pre-segmented graphemes (e.g. with paste-marker awareness).
98
- * When omitted the default Intl.Segmenter is used.
99
- * @returns Array of chunks with text and position information
100
- */
101
- export function wordWrapLine(line: string, maxWidth: number, preSegmented?: Intl.SegmentData[]): TextChunk[] {
102
- if (!line || maxWidth <= 0) {
103
- return [{ text: "", startIndex: 0, endIndex: 0 }];
104
- }
105
-
106
- const lineWidth = visibleWidth(line);
107
- if (lineWidth <= maxWidth) {
108
- return [{ text: line, startIndex: 0, endIndex: line.length }];
109
- }
110
-
111
- const chunks: TextChunk[] = [];
112
- const segments = preSegmented ?? [...baseSegmenter.segment(line)];
113
-
114
- let currentWidth = 0;
115
- let chunkStart = 0;
116
-
117
- // Wrap opportunity: the position after the last whitespace before a non-whitespace
118
- // grapheme, i.e. where a line break is allowed.
119
- let wrapOppIndex = -1;
120
- let wrapOppWidth = 0;
121
-
122
- for (let i = 0; i < segments.length; i++) {
123
- const seg = segments[i]!;
124
- const grapheme = seg.segment;
125
- const gWidth = visibleWidth(grapheme);
126
- const charIndex = seg.index;
127
- const isWs = !isPasteMarker(grapheme) && isWhitespaceChar(grapheme);
128
-
129
- // Overflow check before advancing.
130
- if (currentWidth + gWidth > maxWidth) {
131
- if (wrapOppIndex >= 0 && currentWidth - wrapOppWidth + gWidth <= maxWidth) {
132
- // Backtrack to last wrap opportunity (the remaining content
133
- // plus the current grapheme still fits within maxWidth).
134
- chunks.push({ text: line.slice(chunkStart, wrapOppIndex), startIndex: chunkStart, endIndex: wrapOppIndex });
135
- chunkStart = wrapOppIndex;
136
- currentWidth -= wrapOppWidth;
137
- } else if (chunkStart < charIndex) {
138
- // No viable wrap opportunity: force-break at current position.
139
- // This also handles the case where backtracking to a word
140
- // boundary wouldn't help because the remaining content plus
141
- // the current grapheme (e.g. a wide character) still exceeds
142
- // maxWidth.
143
- chunks.push({ text: line.slice(chunkStart, charIndex), startIndex: chunkStart, endIndex: charIndex });
144
- chunkStart = charIndex;
145
- currentWidth = 0;
146
- }
147
- wrapOppIndex = -1;
148
- }
149
-
150
- if (gWidth > maxWidth) {
151
- // Single atomic segment wider than maxWidth (e.g. paste marker
152
- // in a narrow terminal). Re-wrap it at grapheme granularity.
153
-
154
- // The segment remains logically atomic for cursor
155
- // movement / editing — the split is purely visual for word-wrap layout.
156
- const subChunks = wordWrapLine(grapheme, maxWidth);
157
- for (let j = 0; j < subChunks.length - 1; j++) {
158
- const sc = subChunks[j]!;
159
- chunks.push({ text: sc.text, startIndex: charIndex + sc.startIndex, endIndex: charIndex + sc.endIndex });
160
- }
161
- const last = subChunks[subChunks.length - 1]!;
162
- chunkStart = charIndex + last.startIndex;
163
- currentWidth = visibleWidth(last.text);
164
- wrapOppIndex = -1;
165
- continue;
166
- }
167
-
168
- // Advance.
169
- currentWidth += gWidth;
170
-
171
- // Record wrap opportunity: whitespace followed by non-whitespace.
172
- // Multiple spaces join (no break between them); the break point is
173
- // after the last space before the next word.
174
- const next = segments[i + 1];
175
- if (isWs && next && (isPasteMarker(next.segment) || !isWhitespaceChar(next.segment))) {
176
- wrapOppIndex = next.index;
177
- wrapOppWidth = currentWidth;
178
- }
179
- }
180
-
181
- // Push final chunk.
182
- chunks.push({ text: line.slice(chunkStart), startIndex: chunkStart, endIndex: line.length });
183
-
184
- return chunks;
185
- }
186
-
187
- // Kitty CSI-u sequences for printable keys, including optional shifted/base codepoints.
188
- interface EditorState {
189
- lines: string[];
190
- cursorLine: number;
191
- cursorCol: number;
192
- }
193
-
194
- interface LayoutLine {
195
- text: string;
196
- hasCursor: boolean;
197
- cursorPos?: number;
198
- }
199
-
200
- export interface EditorTheme {
201
- borderColor: (str: string) => string;
202
- selectList: SelectListTheme;
203
- }
204
-
205
- export interface EditorOptions {
206
- paddingX?: number;
207
- autocompleteMaxVisible?: number;
208
- }
209
-
210
- const SLASH_COMMAND_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {
211
- minPrimaryColumnWidth: 12,
212
- maxPrimaryColumnWidth: 32,
213
- };
214
-
215
- const ATTACHMENT_AUTOCOMPLETE_DEBOUNCE_MS = 20;
216
-
217
- export class Editor implements Component, Focusable {
218
- private state: EditorState = {
219
- lines: [""],
220
- cursorLine: 0,
221
- cursorCol: 0,
222
- };
223
-
224
- /** Focusable interface - set by TUI when focus changes */
225
- focused: boolean = false;
226
-
227
- protected tui: TUI;
228
- private theme: EditorTheme;
229
- private paddingX: number = 0;
230
-
231
- // Store last render width for cursor navigation
232
- private lastWidth: number = 80;
233
-
234
- // Vertical scrolling support
235
- private scrollOffset: number = 0;
236
-
237
- // Border color (can be changed dynamically)
238
- public borderColor: (str: string) => string;
239
-
240
- // Autocomplete support
241
- private autocompleteProvider?: AutocompleteProvider;
242
- private autocompleteList?: SelectList;
243
- private autocompleteState: "regular" | "force" | null = null;
244
- private autocompletePrefix: string = "";
245
- private autocompleteMaxVisible: number = 5;
246
- private autocompleteAbort?: AbortController;
247
- private autocompleteDebounceTimer?: ReturnType<typeof setTimeout>;
248
- private autocompleteRequestTask: Promise<void> = Promise.resolve();
249
- private autocompleteStartToken: number = 0;
250
- private autocompleteRequestId: number = 0;
251
-
252
- // Paste tracking for large pastes
253
- private pastes: Map<number, string> = new Map();
254
- private pasteCounter: number = 0;
255
-
256
- // Bracketed paste mode buffering
257
- private pasteBuffer: string = "";
258
- private isInPaste: boolean = false;
259
-
260
- // Prompt history for up/down navigation
261
- private history: string[] = [];
262
- private historyIndex: number = -1; // -1 = not browsing, 0 = most recent, 1 = older, etc.
263
-
264
- // Kill ring for Emacs-style kill/yank operations
265
- private killRing = new KillRing();
266
- private lastAction: "kill" | "yank" | "type-word" | null = null;
267
-
268
- // Character jump mode
269
- private jumpMode: "forward" | "backward" | null = null;
270
-
271
- // Preferred visual column for vertical cursor movement (sticky column)
272
- private preferredVisualCol: number | null = null;
273
-
274
- // When the cursor is snapped to the start of an atomic segment, e.g. a
275
- // paste marker, cursorCol no longer reflects where the cursor would have
276
- // landed. This field stores the pre-snap cursorCol so that the next
277
- // vertical move can resolve it to a visual column on whatever VL it belongs
278
- // to.
279
- private snappedFromCursorCol: number | null = null;
280
-
281
- // Undo support
282
- private undoStack = new UndoStack<EditorState>();
283
-
284
- public onSubmit?: (text: string) => void;
285
- public onChange?: (text: string) => void;
286
- public disableSubmit: boolean = false;
287
-
288
- constructor(tui: TUI, theme: EditorTheme, options: EditorOptions = {}) {
289
- this.tui = tui;
290
- this.theme = theme;
291
- this.borderColor = theme.borderColor;
292
- const paddingX = options.paddingX ?? 0;
293
- this.paddingX = Number.isFinite(paddingX) ? Math.max(0, Math.floor(paddingX)) : 0;
294
- const maxVisible = options.autocompleteMaxVisible ?? 5;
295
- this.autocompleteMaxVisible = Number.isFinite(maxVisible) ? Math.max(3, Math.min(20, Math.floor(maxVisible))) : 5;
296
- }
297
-
298
- /** Set of currently valid paste IDs, for marker-aware segmentation. */
299
- private validPasteIds(): Set<number> {
300
- return new Set(this.pastes.keys());
301
- }
302
-
303
- /** Segment text with paste-marker awareness, only merging markers with valid IDs. */
304
- private segment(text: string): Iterable<Intl.SegmentData> {
305
- return segmentWithMarkers(text, this.validPasteIds());
306
- }
307
-
308
- getPaddingX(): number {
309
- return this.paddingX;
310
- }
311
-
312
- setPaddingX(padding: number): void {
313
- const newPadding = Number.isFinite(padding) ? Math.max(0, Math.floor(padding)) : 0;
314
- if (this.paddingX !== newPadding) {
315
- this.paddingX = newPadding;
316
- this.tui.requestRender();
317
- }
318
- }
319
-
320
- getAutocompleteMaxVisible(): number {
321
- return this.autocompleteMaxVisible;
322
- }
323
-
324
- setAutocompleteMaxVisible(maxVisible: number): void {
325
- const newMaxVisible = Number.isFinite(maxVisible) ? Math.max(3, Math.min(20, Math.floor(maxVisible))) : 5;
326
- if (this.autocompleteMaxVisible !== newMaxVisible) {
327
- this.autocompleteMaxVisible = newMaxVisible;
328
- this.tui.requestRender();
329
- }
330
- }
331
-
332
- setAutocompleteProvider(provider: AutocompleteProvider): void {
333
- this.cancelAutocomplete();
334
- this.autocompleteProvider = provider;
335
- }
336
-
337
- /**
338
- * Add a prompt to history for up/down arrow navigation.
339
- * Called after successful submission.
340
- */
341
- addToHistory(text: string): void {
342
- const trimmed = text.trim();
343
- if (!trimmed) return;
344
- // Don't add consecutive duplicates
345
- if (this.history.length > 0 && this.history[0] === trimmed) return;
346
- this.history.unshift(trimmed);
347
- // Limit history size
348
- if (this.history.length > 100) {
349
- this.history.pop();
350
- }
351
- }
352
-
353
- private isEditorEmpty(): boolean {
354
- return this.state.lines.length === 1 && this.state.lines[0] === "";
355
- }
356
-
357
- private isOnFirstVisualLine(): boolean {
358
- const visualLines = this.buildVisualLineMap(this.lastWidth);
359
- const currentVisualLine = this.findCurrentVisualLine(visualLines);
360
- return currentVisualLine === 0;
361
- }
362
-
363
- private isOnLastVisualLine(): boolean {
364
- const visualLines = this.buildVisualLineMap(this.lastWidth);
365
- const currentVisualLine = this.findCurrentVisualLine(visualLines);
366
- return currentVisualLine === visualLines.length - 1;
367
- }
368
-
369
- private navigateHistory(direction: 1 | -1): void {
370
- this.lastAction = null;
371
- if (this.history.length === 0) return;
372
-
373
- const newIndex = this.historyIndex - direction; // Up(-1) increases index, Down(1) decreases
374
- if (newIndex < -1 || newIndex >= this.history.length) return;
375
-
376
- // Capture state when first entering history browsing mode
377
- if (this.historyIndex === -1 && newIndex >= 0) {
378
- this.pushUndoSnapshot();
379
- }
380
-
381
- this.historyIndex = newIndex;
382
-
383
- if (this.historyIndex === -1) {
384
- // Returned to "current" state - clear editor
385
- this.setTextInternal("");
386
- } else {
387
- this.setTextInternal(this.history[this.historyIndex] || "");
388
- }
389
- }
390
-
391
- /** Internal setText that doesn't reset history state - used by navigateHistory */
392
- private setTextInternal(text: string): void {
393
- const lines = text.split("\n");
394
- this.state.lines = lines.length === 0 ? [""] : lines;
395
- this.state.cursorLine = this.state.lines.length - 1;
396
- this.setCursorCol(this.state.lines[this.state.cursorLine]?.length || 0);
397
- // Reset scroll - render() will adjust to show cursor
398
- this.scrollOffset = 0;
399
-
400
- if (this.onChange) {
401
- this.onChange(this.getText());
402
- }
403
- }
404
-
405
- invalidate(): void {
406
- // No cached state to invalidate currently
407
- }
408
-
409
- render(width: number): string[] {
410
- const maxPadding = Math.max(0, Math.floor((width - 1) / 2));
411
- const paddingX = Math.min(this.paddingX, maxPadding);
412
- const contentWidth = Math.max(1, width - paddingX * 2);
413
-
414
- // Layout width: with padding the cursor can overflow into it,
415
- // without padding we reserve 1 column for the cursor.
416
- const layoutWidth = Math.max(1, contentWidth - (paddingX ? 0 : 1));
417
-
418
- // Store for cursor navigation (must match wrapping width)
419
- this.lastWidth = layoutWidth;
420
-
421
- const horizontal = this.borderColor("─");
422
-
423
- // Layout the text
424
- const layoutLines = this.layoutText(layoutWidth);
425
-
426
- // Calculate max visible lines: 30% of terminal height, minimum 5 lines
427
- const terminalRows = this.tui.terminal.rows;
428
- const maxVisibleLines = Math.max(5, Math.floor(terminalRows * 0.3));
429
-
430
- // Find the cursor line index in layoutLines
431
- let cursorLineIndex = layoutLines.findIndex((line) => line.hasCursor);
432
- if (cursorLineIndex === -1) cursorLineIndex = 0;
433
-
434
- // Adjust scroll offset to keep cursor visible
435
- if (cursorLineIndex < this.scrollOffset) {
436
- this.scrollOffset = cursorLineIndex;
437
- } else if (cursorLineIndex >= this.scrollOffset + maxVisibleLines) {
438
- this.scrollOffset = cursorLineIndex - maxVisibleLines + 1;
439
- }
440
-
441
- // Clamp scroll offset to valid range
442
- const maxScrollOffset = Math.max(0, layoutLines.length - maxVisibleLines);
443
- this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxScrollOffset));
444
-
445
- // Get visible lines slice
446
- const visibleLines = layoutLines.slice(this.scrollOffset, this.scrollOffset + maxVisibleLines);
447
-
448
- const result: string[] = [];
449
- const leftPadding = " ".repeat(paddingX);
450
- const rightPadding = leftPadding;
451
-
452
- // Render top border (with scroll indicator if scrolled down)
453
- if (this.scrollOffset > 0) {
454
- const indicator = `─── ↑ ${this.scrollOffset} more `;
455
- const remaining = width - visibleWidth(indicator);
456
- if (remaining >= 0) {
457
- result.push(this.borderColor(indicator + "─".repeat(remaining)));
458
- } else {
459
- result.push(this.borderColor(truncateToWidth(indicator, width)));
460
- }
461
- } else {
462
- result.push(horizontal.repeat(width));
463
- }
464
-
465
- // Render each visible layout line
466
- // Emit hardware cursor marker only when focused and not showing autocomplete
467
- const emitCursorMarker = this.focused && !this.autocompleteState;
468
-
469
- for (const layoutLine of visibleLines) {
470
- let displayText = layoutLine.text;
471
- let lineVisibleWidth = visibleWidth(layoutLine.text);
472
- let cursorInPadding = false;
473
-
474
- // Add cursor if this line has it
475
- if (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {
476
- const before = displayText.slice(0, layoutLine.cursorPos);
477
- const after = displayText.slice(layoutLine.cursorPos);
478
-
479
- // Hardware cursor marker (zero-width, emitted before fake cursor for IME positioning)
480
- const marker = emitCursorMarker ? CURSOR_MARKER : "";
481
-
482
- if (after.length > 0) {
483
- // Cursor is on a character (grapheme) - replace it with highlighted version
484
- // Get the first grapheme from 'after'
485
- const afterGraphemes = [...this.segment(after)];
486
- const firstGrapheme = afterGraphemes[0]?.segment || "";
487
- const restAfter = after.slice(firstGrapheme.length);
488
- const cursor = `\x1b[7m${firstGrapheme}\x1b[0m`;
489
- displayText = before + marker + cursor + restAfter;
490
- // lineVisibleWidth stays the same - we're replacing, not adding
491
- } else {
492
- // Cursor is at the end - add highlighted space
493
- const cursor = "\x1b[7m \x1b[0m";
494
- displayText = before + marker + cursor;
495
- lineVisibleWidth = lineVisibleWidth + 1;
496
- // If cursor overflows content width into the padding, flag it
497
- if (lineVisibleWidth > contentWidth && paddingX > 0) {
498
- cursorInPadding = true;
499
- }
500
- }
501
- }
502
-
503
- // Calculate padding based on actual visible width
504
- const padding = " ".repeat(Math.max(0, contentWidth - lineVisibleWidth));
505
- const lineRightPadding = cursorInPadding ? rightPadding.slice(1) : rightPadding;
506
-
507
- // Render the line (no side borders, just horizontal lines above and below)
508
- result.push(`${leftPadding}${displayText}${padding}${lineRightPadding}`);
509
- }
510
-
511
- // Render bottom border (with scroll indicator if more content below)
512
- const linesBelow = layoutLines.length - (this.scrollOffset + visibleLines.length);
513
- if (linesBelow > 0) {
514
- const indicator = `─── ↓ ${linesBelow} more `;
515
- const remaining = width - visibleWidth(indicator);
516
- result.push(this.borderColor(indicator + "─".repeat(Math.max(0, remaining))));
517
- } else {
518
- result.push(horizontal.repeat(width));
519
- }
520
-
521
- // Add autocomplete list if active
522
- if (this.autocompleteState && this.autocompleteList) {
523
- const autocompleteResult = this.autocompleteList.render(contentWidth);
524
- for (const line of autocompleteResult) {
525
- const lineWidth = visibleWidth(line);
526
- const linePadding = " ".repeat(Math.max(0, contentWidth - lineWidth));
527
- result.push(`${leftPadding}${line}${linePadding}${rightPadding}`);
528
- }
529
- }
530
-
531
- return result;
532
- }
533
-
534
- handleInput(data: string): void {
535
- const kb = getKeybindings();
536
-
537
- // Handle character jump mode (awaiting next character to jump to)
538
- if (this.jumpMode !== null) {
539
- // Cancel if the hotkey is pressed again
540
- if (kb.matches(data, "tui.editor.jumpForward") || kb.matches(data, "tui.editor.jumpBackward")) {
541
- this.jumpMode = null;
542
- return;
543
- }
544
-
545
- const printable = decodePrintableKey(data) ?? (data.charCodeAt(0) >= 32 ? data : undefined);
546
- if (printable !== undefined) {
547
- // Printable character - perform the jump
548
- const direction = this.jumpMode;
549
- this.jumpMode = null;
550
- this.jumpToChar(printable, direction);
551
- return;
552
- }
553
-
554
- // Control character - cancel and fall through to normal handling
555
- this.jumpMode = null;
556
- }
557
-
558
- // Handle bracketed paste mode
559
- if (data.includes("\x1b[200~")) {
560
- this.isInPaste = true;
561
- this.pasteBuffer = "";
562
- data = data.replace("\x1b[200~", "");
563
- }
564
-
565
- if (this.isInPaste) {
566
- this.pasteBuffer += data;
567
- const endIndex = this.pasteBuffer.indexOf("\x1b[201~");
568
- if (endIndex !== -1) {
569
- const pasteContent = this.pasteBuffer.substring(0, endIndex);
570
- if (pasteContent.length > 0) {
571
- this.handlePaste(pasteContent);
572
- }
573
- this.isInPaste = false;
574
- const remaining = this.pasteBuffer.substring(endIndex + 6);
575
- this.pasteBuffer = "";
576
- if (remaining.length > 0) {
577
- this.handleInput(remaining);
578
- }
579
- return;
580
- }
581
- return;
582
- }
583
-
584
- // Ctrl+C - let parent handle (exit/clear)
585
- if (kb.matches(data, "tui.input.copy")) {
586
- return;
587
- }
588
-
589
- // Undo
590
- if (kb.matches(data, "tui.editor.undo")) {
591
- this.undo();
592
- return;
593
- }
594
-
595
- // Handle autocomplete mode
596
- if (this.autocompleteState && this.autocompleteList) {
597
- if (kb.matches(data, "tui.select.cancel")) {
598
- this.cancelAutocomplete();
599
- return;
600
- }
601
-
602
- if (kb.matches(data, "tui.select.up") || kb.matches(data, "tui.select.down")) {
603
- this.autocompleteList.handleInput(data);
604
- return;
605
- }
606
-
607
- if (kb.matches(data, "tui.input.tab")) {
608
- const selected = this.autocompleteList.getSelectedItem();
609
- if (selected && this.autocompleteProvider) {
610
- this.pushUndoSnapshot();
611
- this.lastAction = null;
612
- const result = this.autocompleteProvider.applyCompletion(
613
- this.state.lines,
614
- this.state.cursorLine,
615
- this.state.cursorCol,
616
- selected,
617
- this.autocompletePrefix,
618
- );
619
- this.state.lines = result.lines;
620
- this.state.cursorLine = result.cursorLine;
621
- this.setCursorCol(result.cursorCol);
622
- this.cancelAutocomplete();
623
- if (this.onChange) this.onChange(this.getText());
624
- }
625
- return;
626
- }
627
-
628
- if (kb.matches(data, "tui.select.confirm")) {
629
- const selected = this.autocompleteList.getSelectedItem();
630
- if (selected && this.autocompleteProvider) {
631
- this.pushUndoSnapshot();
632
- this.lastAction = null;
633
- const result = this.autocompleteProvider.applyCompletion(
634
- this.state.lines,
635
- this.state.cursorLine,
636
- this.state.cursorCol,
637
- selected,
638
- this.autocompletePrefix,
639
- );
640
- this.state.lines = result.lines;
641
- this.state.cursorLine = result.cursorLine;
642
- this.setCursorCol(result.cursorCol);
643
-
644
- if (this.autocompletePrefix.startsWith("/")) {
645
- this.cancelAutocomplete();
646
- // Fall through to submit
647
- } else {
648
- this.cancelAutocomplete();
649
- if (this.onChange) this.onChange(this.getText());
650
- return;
651
- }
652
- }
653
- }
654
- }
655
-
656
- // Tab - trigger completion
657
- if (kb.matches(data, "tui.input.tab") && !this.autocompleteState) {
658
- this.handleTabCompletion();
659
- return;
660
- }
661
-
662
- // Deletion actions
663
- if (kb.matches(data, "tui.editor.deleteToLineEnd")) {
664
- this.deleteToEndOfLine();
665
- return;
666
- }
667
- if (kb.matches(data, "tui.editor.deleteToLineStart")) {
668
- this.deleteToStartOfLine();
669
- return;
670
- }
671
- if (kb.matches(data, "tui.editor.deleteWordBackward")) {
672
- this.deleteWordBackwards();
673
- return;
674
- }
675
- if (kb.matches(data, "tui.editor.deleteWordForward")) {
676
- this.deleteWordForward();
677
- return;
678
- }
679
- if (kb.matches(data, "tui.editor.deleteCharBackward") || matchesKey(data, "shift+backspace")) {
680
- this.handleBackspace();
681
- return;
682
- }
683
- if (kb.matches(data, "tui.editor.deleteCharForward") || matchesKey(data, "shift+delete")) {
684
- this.handleForwardDelete();
685
- return;
686
- }
687
-
688
- // Kill ring actions
689
- if (kb.matches(data, "tui.editor.yank")) {
690
- this.yank();
691
- return;
692
- }
693
- if (kb.matches(data, "tui.editor.yankPop")) {
694
- this.yankPop();
695
- return;
696
- }
697
-
698
- // Cursor movement actions
699
- if (kb.matches(data, "tui.editor.cursorLineStart")) {
700
- this.moveToLineStart();
701
- return;
702
- }
703
- if (kb.matches(data, "tui.editor.cursorLineEnd")) {
704
- this.moveToLineEnd();
705
- return;
706
- }
707
- if (kb.matches(data, "tui.editor.cursorWordLeft")) {
708
- this.moveWordBackwards();
709
- return;
710
- }
711
- if (kb.matches(data, "tui.editor.cursorWordRight")) {
712
- this.moveWordForwards();
713
- return;
714
- }
715
-
716
- // New line
717
- if (
718
- kb.matches(data, "tui.input.newLine") ||
719
- (data.charCodeAt(0) === 10 && data.length > 1) ||
720
- data === "\x1b\r" ||
721
- data === "\x1b[13;2~" ||
722
- (data.length > 1 && data.includes("\x1b") && data.includes("\r")) ||
723
- (data === "\n" && data.length === 1)
724
- ) {
725
- if (this.shouldSubmitOnBackslashEnter(data, kb)) {
726
- this.handleBackspace();
727
- this.submitValue();
728
- return;
729
- }
730
- this.addNewLine();
731
- return;
732
- }
733
-
734
- // Submit (Enter)
735
- if (kb.matches(data, "tui.input.submit")) {
736
- if (this.disableSubmit) return;
737
-
738
- // Workaround for terminals without Shift+Enter support:
739
- // If char before cursor is \, delete it and insert newline instead of submitting.
740
- const currentLine = this.state.lines[this.state.cursorLine] || "";
741
- if (this.state.cursorCol > 0 && currentLine[this.state.cursorCol - 1] === "\\") {
742
- this.handleBackspace();
743
- this.addNewLine();
744
- return;
745
- }
746
-
747
- this.submitValue();
748
- return;
749
- }
750
-
751
- // Arrow key navigation (with history support)
752
- if (kb.matches(data, "tui.editor.cursorUp")) {
753
- if (this.isEditorEmpty()) {
754
- this.navigateHistory(-1);
755
- } else if (this.historyIndex > -1 && this.isOnFirstVisualLine()) {
756
- this.navigateHistory(-1);
757
- } else if (this.isOnFirstVisualLine()) {
758
- // Already at top - jump to start of line
759
- this.moveToLineStart();
760
- } else {
761
- this.moveCursor(-1, 0);
762
- }
763
- return;
764
- }
765
- if (kb.matches(data, "tui.editor.cursorDown")) {
766
- if (this.historyIndex > -1 && this.isOnLastVisualLine()) {
767
- this.navigateHistory(1);
768
- } else if (this.isOnLastVisualLine()) {
769
- // Already at bottom - jump to end of line
770
- this.moveToLineEnd();
771
- } else {
772
- this.moveCursor(1, 0);
773
- }
774
- return;
775
- }
776
- if (kb.matches(data, "tui.editor.cursorRight")) {
777
- this.moveCursor(0, 1);
778
- return;
779
- }
780
- if (kb.matches(data, "tui.editor.cursorLeft")) {
781
- this.moveCursor(0, -1);
782
- return;
783
- }
784
-
785
- // Page up/down - scroll by page and move cursor
786
- if (kb.matches(data, "tui.editor.pageUp")) {
787
- this.pageScroll(-1);
788
- return;
789
- }
790
- if (kb.matches(data, "tui.editor.pageDown")) {
791
- this.pageScroll(1);
792
- return;
793
- }
794
-
795
- // Character jump mode triggers
796
- if (kb.matches(data, "tui.editor.jumpForward")) {
797
- this.jumpMode = "forward";
798
- return;
799
- }
800
- if (kb.matches(data, "tui.editor.jumpBackward")) {
801
- this.jumpMode = "backward";
802
- return;
803
- }
804
-
805
- // Shift+Space - insert regular space
806
- if (matchesKey(data, "shift+space")) {
807
- this.insertCharacter(" ");
808
- return;
809
- }
810
-
811
- const printable = decodePrintableKey(data);
812
- if (printable !== undefined) {
813
- this.insertCharacter(printable);
814
- return;
815
- }
816
-
817
- // Regular characters
818
- if (data.charCodeAt(0) >= 32) {
819
- this.insertCharacter(data);
820
- }
821
- }
822
-
823
- private layoutText(contentWidth: number): LayoutLine[] {
824
- const layoutLines: LayoutLine[] = [];
825
-
826
- if (this.state.lines.length === 0 || (this.state.lines.length === 1 && this.state.lines[0] === "")) {
827
- // Empty editor
828
- layoutLines.push({
829
- text: "",
830
- hasCursor: true,
831
- cursorPos: 0,
832
- });
833
- return layoutLines;
834
- }
835
-
836
- // Process each logical line
837
- for (let i = 0; i < this.state.lines.length; i++) {
838
- const line = this.state.lines[i] || "";
839
- const isCurrentLine = i === this.state.cursorLine;
840
- const lineVisibleWidth = visibleWidth(line);
841
-
842
- if (lineVisibleWidth <= contentWidth) {
843
- // Line fits in one layout line
844
- if (isCurrentLine) {
845
- layoutLines.push({
846
- text: line,
847
- hasCursor: true,
848
- cursorPos: this.state.cursorCol,
849
- });
850
- } else {
851
- layoutLines.push({
852
- text: line,
853
- hasCursor: false,
854
- });
855
- }
856
- } else {
857
- // Line needs wrapping - use word-aware wrapping
858
- const chunks = wordWrapLine(line, contentWidth, [...this.segment(line)]);
859
-
860
- for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
861
- const chunk = chunks[chunkIndex];
862
- if (!chunk) continue;
863
-
864
- const cursorPos = this.state.cursorCol;
865
- const isLastChunk = chunkIndex === chunks.length - 1;
866
-
867
- // Determine if cursor is in this chunk
868
- // For word-wrapped chunks, we need to handle the case where
869
- // cursor might be in trimmed whitespace at end of chunk
870
- let hasCursorInChunk = false;
871
- let adjustedCursorPos = 0;
872
-
873
- if (isCurrentLine) {
874
- if (isLastChunk) {
875
- // Last chunk: cursor belongs here if >= startIndex
876
- hasCursorInChunk = cursorPos >= chunk.startIndex;
877
- adjustedCursorPos = cursorPos - chunk.startIndex;
878
- } else {
879
- // Non-last chunk: cursor belongs here if in range [startIndex, endIndex)
880
- // But we need to handle the visual position in the trimmed text
881
- hasCursorInChunk = cursorPos >= chunk.startIndex && cursorPos < chunk.endIndex;
882
- if (hasCursorInChunk) {
883
- adjustedCursorPos = cursorPos - chunk.startIndex;
884
- // Clamp to text length (in case cursor was in trimmed whitespace)
885
- if (adjustedCursorPos > chunk.text.length) {
886
- adjustedCursorPos = chunk.text.length;
887
- }
888
- }
889
- }
890
- }
891
-
892
- if (hasCursorInChunk) {
893
- layoutLines.push({
894
- text: chunk.text,
895
- hasCursor: true,
896
- cursorPos: adjustedCursorPos,
897
- });
898
- } else {
899
- layoutLines.push({
900
- text: chunk.text,
901
- hasCursor: false,
902
- });
903
- }
904
- }
905
- }
906
- }
907
-
908
- return layoutLines;
909
- }
910
-
911
- getText(): string {
912
- return this.state.lines.join("\n");
913
- }
914
-
915
- private expandPasteMarkers(text: string): string {
916
- let result = text;
917
- for (const [pasteId, pasteContent] of this.pastes) {
918
- const markerRegex = new RegExp(`\\[paste #${pasteId}( (\\+\\d+ lines|\\d+ chars))?\\]`, "g");
919
- result = result.replace(markerRegex, () => pasteContent);
920
- }
921
- return result;
922
- }
923
-
924
- /**
925
- * Get text with paste markers expanded to their actual content.
926
- * Use this when you need the full content (e.g., for external editor).
927
- */
928
- getExpandedText(): string {
929
- return this.expandPasteMarkers(this.state.lines.join("\n"));
930
- }
931
-
932
- getLines(): string[] {
933
- return [...this.state.lines];
934
- }
935
-
936
- getCursor(): { line: number; col: number } {
937
- return { line: this.state.cursorLine, col: this.state.cursorCol };
938
- }
939
-
940
- setText(text: string): void {
941
- this.cancelAutocomplete();
942
- this.lastAction = null;
943
- this.historyIndex = -1; // Exit history browsing mode
944
- const normalized = this.normalizeText(text);
945
- // Push undo snapshot if content differs (makes programmatic changes undoable)
946
- if (this.getText() !== normalized) {
947
- this.pushUndoSnapshot();
948
- }
949
- this.setTextInternal(normalized);
950
- }
951
-
952
- /**
953
- * Insert text at the current cursor position.
954
- * Used for programmatic insertion (e.g., clipboard image markers).
955
- * This is atomic for undo - single undo restores entire pre-insert state.
956
- */
957
- insertTextAtCursor(text: string): void {
958
- if (!text) return;
959
- this.cancelAutocomplete();
960
- this.pushUndoSnapshot();
961
- this.lastAction = null;
962
- this.historyIndex = -1;
963
- this.insertTextAtCursorInternal(text);
964
- }
965
-
966
- /**
967
- * Normalize text for editor storage:
968
- * - Normalize line endings (\r\n and \r -> \n)
969
- * - Expand tabs to 4 spaces
970
- */
971
- private normalizeText(text: string): string {
972
- return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\t/g, " ");
973
- }
974
-
975
- /**
976
- * Internal text insertion at cursor. Handles single and multi-line text.
977
- * Does not push undo snapshots or trigger autocomplete - caller is responsible.
978
- * Normalizes line endings and calls onChange once at the end.
979
- */
980
- private insertTextAtCursorInternal(text: string): void {
981
- if (!text) return;
982
-
983
- // Normalize line endings and tabs
984
- const normalized = this.normalizeText(text);
985
- const insertedLines = normalized.split("\n");
986
-
987
- const currentLine = this.state.lines[this.state.cursorLine] || "";
988
- const beforeCursor = currentLine.slice(0, this.state.cursorCol);
989
- const afterCursor = currentLine.slice(this.state.cursorCol);
990
-
991
- if (insertedLines.length === 1) {
992
- // Single line - insert at cursor position
993
- this.state.lines[this.state.cursorLine] = beforeCursor + normalized + afterCursor;
994
- this.setCursorCol(this.state.cursorCol + normalized.length);
995
- } else {
996
- // Multi-line insertion
997
- this.state.lines = [
998
- // All lines before current line
999
- ...this.state.lines.slice(0, this.state.cursorLine),
1000
-
1001
- // The first inserted line merged with text before cursor
1002
- beforeCursor + insertedLines[0],
1003
-
1004
- // All middle inserted lines
1005
- ...insertedLines.slice(1, -1),
1006
-
1007
- // The last inserted line with text after cursor
1008
- insertedLines[insertedLines.length - 1] + afterCursor,
1009
-
1010
- // All lines after current line
1011
- ...this.state.lines.slice(this.state.cursorLine + 1),
1012
- ];
1013
-
1014
- this.state.cursorLine += insertedLines.length - 1;
1015
- this.setCursorCol((insertedLines[insertedLines.length - 1] || "").length);
1016
- }
1017
-
1018
- if (this.onChange) {
1019
- this.onChange(this.getText());
1020
- }
1021
- }
1022
-
1023
- // All the editor methods from before...
1024
- private insertCharacter(char: string, skipUndoCoalescing?: boolean): void {
1025
- this.historyIndex = -1; // Exit history browsing mode
1026
-
1027
- // Undo coalescing (fish-style):
1028
- // - Consecutive word chars coalesce into one undo unit
1029
- // - Space captures state before itself (so undo removes space+following word together)
1030
- // - Each space is separately undoable
1031
- // Skip coalescing when called from atomic operations (e.g., handlePaste)
1032
- if (!skipUndoCoalescing) {
1033
- if (isWhitespaceChar(char) || this.lastAction !== "type-word") {
1034
- this.pushUndoSnapshot();
1035
- }
1036
- this.lastAction = "type-word";
1037
- }
1038
-
1039
- const line = this.state.lines[this.state.cursorLine] || "";
1040
-
1041
- const before = line.slice(0, this.state.cursorCol);
1042
- const after = line.slice(this.state.cursorCol);
1043
-
1044
- this.state.lines[this.state.cursorLine] = before + char + after;
1045
- this.setCursorCol(this.state.cursorCol + char.length);
1046
-
1047
- if (this.onChange) {
1048
- this.onChange(this.getText());
1049
- }
1050
-
1051
- // Check if we should trigger or update autocomplete
1052
- if (!this.autocompleteState) {
1053
- // Auto-trigger for "/" at the start of a line (slash commands)
1054
- if (char === "/" && this.isAtStartOfMessage()) {
1055
- this.tryTriggerAutocomplete();
1056
- }
1057
- // Auto-trigger for symbol-based completion like @ or # at token boundaries
1058
- else if (char === "@" || char === "#") {
1059
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1060
- const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
1061
- const charBeforeSymbol = textBeforeCursor[textBeforeCursor.length - 2];
1062
- if (textBeforeCursor.length === 1 || charBeforeSymbol === " " || charBeforeSymbol === "\t") {
1063
- this.tryTriggerAutocomplete();
1064
- }
1065
- }
1066
- // Also auto-trigger when typing letters in a slash command or symbol completion context
1067
- else if (/[a-zA-Z0-9.\-_]/.test(char)) {
1068
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1069
- const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
1070
- // Check if we're in a slash command (with or without space for arguments)
1071
- if (this.isInSlashCommandContext(textBeforeCursor)) {
1072
- this.tryTriggerAutocomplete();
1073
- }
1074
- // Check if we're in a symbol-based completion context like @ or #
1075
- else if (textBeforeCursor.match(/(?:^|[\s])[@#][^\s]*$/)) {
1076
- this.tryTriggerAutocomplete();
1077
- }
1078
- }
1079
- } else {
1080
- this.updateAutocomplete();
1081
- }
1082
- }
1083
-
1084
- private handlePaste(pastedText: string): void {
1085
- this.cancelAutocomplete();
1086
- this.historyIndex = -1; // Exit history browsing mode
1087
- this.lastAction = null;
1088
-
1089
- this.pushUndoSnapshot();
1090
-
1091
- // Clean the pasted text: normalize line endings, expand tabs
1092
- const cleanText = this.normalizeText(pastedText);
1093
-
1094
- // Filter out non-printable characters except newlines
1095
- let filteredText = cleanText
1096
- .split("")
1097
- .filter((char) => char === "\n" || char.charCodeAt(0) >= 32)
1098
- .join("");
1099
-
1100
- // If pasting a file path (starts with /, ~, or .) and the character before
1101
- // the cursor is a word character, prepend a space for better readability
1102
- if (/^[/~.]/.test(filteredText)) {
1103
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1104
- const charBeforeCursor = this.state.cursorCol > 0 ? currentLine[this.state.cursorCol - 1] : "";
1105
- if (charBeforeCursor && /\w/.test(charBeforeCursor)) {
1106
- filteredText = ` ${filteredText}`;
1107
- }
1108
- }
1109
-
1110
- // Split into lines to check for large paste
1111
- const pastedLines = filteredText.split("\n");
1112
-
1113
- // Check if this is a large paste (> 10 lines or > 1000 characters)
1114
- const totalChars = filteredText.length;
1115
- if (pastedLines.length > 10 || totalChars > 1000) {
1116
- // Store the paste and insert a marker
1117
- this.pasteCounter++;
1118
- const pasteId = this.pasteCounter;
1119
- this.pastes.set(pasteId, filteredText);
1120
-
1121
- // Insert marker like "[paste #1 +123 lines]" or "[paste #1 1234 chars]"
1122
- const marker =
1123
- pastedLines.length > 10
1124
- ? `[paste #${pasteId} +${pastedLines.length} lines]`
1125
- : `[paste #${pasteId} ${totalChars} chars]`;
1126
- this.insertTextAtCursorInternal(marker);
1127
- return;
1128
- }
1129
-
1130
- if (pastedLines.length === 1) {
1131
- // Single line - insert atomically (do not trigger autocomplete during paste)
1132
- this.insertTextAtCursorInternal(filteredText);
1133
- return;
1134
- }
1135
-
1136
- // Multi-line paste - use direct state manipulation
1137
- this.insertTextAtCursorInternal(filteredText);
1138
- }
1139
-
1140
- private addNewLine(): void {
1141
- this.cancelAutocomplete();
1142
- this.historyIndex = -1; // Exit history browsing mode
1143
- this.lastAction = null;
1144
-
1145
- this.pushUndoSnapshot();
1146
-
1147
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1148
-
1149
- const before = currentLine.slice(0, this.state.cursorCol);
1150
- const after = currentLine.slice(this.state.cursorCol);
1151
-
1152
- // Split current line
1153
- this.state.lines[this.state.cursorLine] = before;
1154
- this.state.lines.splice(this.state.cursorLine + 1, 0, after);
1155
-
1156
- // Move cursor to start of new line
1157
- this.state.cursorLine++;
1158
- this.setCursorCol(0);
1159
-
1160
- if (this.onChange) {
1161
- this.onChange(this.getText());
1162
- }
1163
- }
1164
-
1165
- private shouldSubmitOnBackslashEnter(data: string, kb: ReturnType<typeof getKeybindings>): boolean {
1166
- if (this.disableSubmit) return false;
1167
- if (!matchesKey(data, "enter")) return false;
1168
- const submitKeys = kb.getKeys("tui.input.submit");
1169
- const hasShiftEnter = submitKeys.includes("shift+enter") || submitKeys.includes("shift+return");
1170
- if (!hasShiftEnter) return false;
1171
-
1172
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1173
- return this.state.cursorCol > 0 && currentLine[this.state.cursorCol - 1] === "\\";
1174
- }
1175
-
1176
- private submitValue(): void {
1177
- this.cancelAutocomplete();
1178
- const result = this.expandPasteMarkers(this.state.lines.join("\n")).trim();
1179
-
1180
- this.state = { lines: [""], cursorLine: 0, cursorCol: 0 };
1181
- this.pastes.clear();
1182
- this.pasteCounter = 0;
1183
- this.historyIndex = -1;
1184
- this.scrollOffset = 0;
1185
- this.undoStack.clear();
1186
- this.lastAction = null;
1187
-
1188
- if (this.onChange) this.onChange("");
1189
- if (this.onSubmit) this.onSubmit(result);
1190
- }
1191
-
1192
- private handleBackspace(): void {
1193
- this.historyIndex = -1; // Exit history browsing mode
1194
- this.lastAction = null;
1195
-
1196
- if (this.state.cursorCol > 0) {
1197
- this.pushUndoSnapshot();
1198
-
1199
- // Delete grapheme before cursor (handles emojis, combining characters, etc.)
1200
- const line = this.state.lines[this.state.cursorLine] || "";
1201
- const beforeCursor = line.slice(0, this.state.cursorCol);
1202
-
1203
- // Find the last grapheme in the text before cursor
1204
- const graphemes = [...this.segment(beforeCursor)];
1205
- const lastGrapheme = graphemes[graphemes.length - 1];
1206
- const graphemeLength = lastGrapheme ? lastGrapheme.segment.length : 1;
1207
-
1208
- const before = line.slice(0, this.state.cursorCol - graphemeLength);
1209
- const after = line.slice(this.state.cursorCol);
1210
-
1211
- this.state.lines[this.state.cursorLine] = before + after;
1212
- this.setCursorCol(this.state.cursorCol - graphemeLength);
1213
- } else if (this.state.cursorLine > 0) {
1214
- this.pushUndoSnapshot();
1215
-
1216
- // Merge with previous line
1217
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1218
- const previousLine = this.state.lines[this.state.cursorLine - 1] || "";
1219
-
1220
- this.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;
1221
- this.state.lines.splice(this.state.cursorLine, 1);
1222
-
1223
- this.state.cursorLine--;
1224
- this.setCursorCol(previousLine.length);
1225
- }
1226
-
1227
- if (this.onChange) {
1228
- this.onChange(this.getText());
1229
- }
1230
-
1231
- // Update or re-trigger autocomplete after backspace
1232
- if (this.autocompleteState) {
1233
- this.updateAutocomplete();
1234
- } else {
1235
- // If autocomplete was cancelled (no matches), re-trigger if we're in a completable context
1236
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1237
- const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
1238
- // Slash command context
1239
- if (this.isInSlashCommandContext(textBeforeCursor)) {
1240
- this.tryTriggerAutocomplete();
1241
- }
1242
- // Symbol-based completion context like @ or #
1243
- else if (textBeforeCursor.match(/(?:^|[\s])[@#][^\s]*$/)) {
1244
- this.tryTriggerAutocomplete();
1245
- }
1246
- }
1247
- }
1248
-
1249
- /**
1250
- * Set cursor column and clear preferredVisualCol.
1251
- * Use this for all non-vertical cursor movements to reset sticky column behavior.
1252
- */
1253
- private setCursorCol(col: number): void {
1254
- this.state.cursorCol = col;
1255
- this.preferredVisualCol = null;
1256
- this.snappedFromCursorCol = null;
1257
- }
1258
-
1259
- /**
1260
- * Move cursor to a target visual line, applying sticky column logic.
1261
- * Shared by moveCursor() and pageScroll().
1262
- */
1263
- private moveToVisualLine(
1264
- visualLines: Array<{ logicalLine: number; startCol: number; length: number }>,
1265
- currentVisualLine: number,
1266
- targetVisualLine: number,
1267
- ): void {
1268
- const currentVL = visualLines[currentVisualLine];
1269
- const targetVL = visualLines[targetVisualLine];
1270
- if (!(currentVL && targetVL)) return;
1271
-
1272
- // When the cursor was snapped to a segment start, resolve the pre-snap
1273
- // position against the VL it belongs to. This gives the correct visual
1274
- // column even after a resize reshuffles VLs.
1275
- let currentVisualCol: number;
1276
- if (this.snappedFromCursorCol !== null) {
1277
- const vlIndex = this.findVisualLineAt(visualLines, currentVL.logicalLine, this.snappedFromCursorCol);
1278
- currentVisualCol = this.snappedFromCursorCol - visualLines[vlIndex].startCol;
1279
- } else {
1280
- currentVisualCol = this.state.cursorCol - currentVL.startCol;
1281
- }
1282
-
1283
- // For non-last segments, clamp to length-1 to stay within the segment
1284
- const isLastSourceSegment =
1285
- currentVisualLine === visualLines.length - 1 ||
1286
- visualLines[currentVisualLine + 1]?.logicalLine !== currentVL.logicalLine;
1287
- const sourceMaxVisualCol = isLastSourceSegment ? currentVL.length : Math.max(0, currentVL.length - 1);
1288
-
1289
- const isLastTargetSegment =
1290
- targetVisualLine === visualLines.length - 1 ||
1291
- visualLines[targetVisualLine + 1]?.logicalLine !== targetVL.logicalLine;
1292
- const targetMaxVisualCol = isLastTargetSegment ? targetVL.length : Math.max(0, targetVL.length - 1);
1293
-
1294
- const moveToVisualCol = this.computeVerticalMoveColumn(currentVisualCol, sourceMaxVisualCol, targetMaxVisualCol);
1295
-
1296
- // Set cursor position
1297
- this.state.cursorLine = targetVL.logicalLine;
1298
- const targetCol = targetVL.startCol + moveToVisualCol;
1299
- const logicalLine = this.state.lines[targetVL.logicalLine] || "";
1300
- this.state.cursorCol = Math.min(targetCol, logicalLine.length);
1301
-
1302
- // Snap cursor to atomic segment boundary (e.g. paste markers)
1303
- // so the cursor never lands in the middle of a multi-grapheme unit.
1304
- // Single-grapheme segments don't need snapping.
1305
- const segments = [...this.segment(logicalLine)];
1306
- for (const seg of segments) {
1307
- if (seg.index > this.state.cursorCol) break;
1308
- if (seg.segment.length <= 1) continue;
1309
- if (this.state.cursorCol < seg.index + seg.segment.length) {
1310
- const isContinuation = seg.index < targetVL.startCol;
1311
- const isMovingDown = targetVisualLine > currentVisualLine;
1312
-
1313
- if (isContinuation && isMovingDown) {
1314
- // The segment started on a previous visual line, and we
1315
- // already visited it on the way down. Skip all remaining
1316
- // continuation VLs and land on the first VL past it.
1317
- const segEnd = seg.index + seg.segment.length;
1318
- let next = targetVisualLine + 1;
1319
- while (
1320
- next < visualLines.length &&
1321
- visualLines[next].logicalLine === targetVL.logicalLine &&
1322
- visualLines[next].startCol < segEnd
1323
- ) {
1324
- next++;
1325
- }
1326
- if (next < visualLines.length) {
1327
- this.moveToVisualLine(visualLines, currentVisualLine, next);
1328
- return;
1329
- }
1330
- }
1331
-
1332
- // Snap to the start of the segment so it gets highlighted.
1333
- // Store the pre-snap position so the next vertical move can
1334
- // resolve it to the correct visual column.
1335
- this.snappedFromCursorCol = this.state.cursorCol;
1336
- this.state.cursorCol = seg.index;
1337
- return;
1338
- }
1339
- }
1340
-
1341
- // No snap occurred – we moved out of the atomic segment.
1342
- this.snappedFromCursorCol = null;
1343
- }
1344
-
1345
- /**
1346
- * Compute the target visual column for vertical cursor movement.
1347
- * Implements the sticky column decision table:
1348
- *
1349
- * | P | S | T | U | Scenario | Set Preferred | Move To |
1350
- * |---|---|---|---| ---------------------------------------------------- |---------------|-------------|
1351
- * | 0 | * | 0 | - | Start nav, target fits | null | current |
1352
- * | 0 | * | 1 | - | Start nav, target shorter | current | target end |
1353
- * | 1 | 0 | 0 | 0 | Clamped, target fits preferred | null | preferred |
1354
- * | 1 | 0 | 0 | 1 | Clamped, target longer but still can't fit preferred | keep | target end |
1355
- * | 1 | 0 | 1 | - | Clamped, target even shorter | keep | target end |
1356
- * | 1 | 1 | 0 | - | Rewrapped, target fits current | null | current |
1357
- * | 1 | 1 | 1 | - | Rewrapped, target shorter than current | current | target end |
1358
- *
1359
- * Where:
1360
- * - P = preferred col is set
1361
- * - S = cursor in middle of source line (not clamped to end)
1362
- * - T = target line shorter than current visual col
1363
- * - U = target line shorter than preferred col
1364
- */
1365
- private computeVerticalMoveColumn(
1366
- currentVisualCol: number,
1367
- sourceMaxVisualCol: number,
1368
- targetMaxVisualCol: number,
1369
- ): number {
1370
- const hasPreferred = this.preferredVisualCol !== null; // P
1371
- const cursorInMiddle = currentVisualCol < sourceMaxVisualCol; // S
1372
- const targetTooShort = targetMaxVisualCol < currentVisualCol; // T
1373
-
1374
- if (!hasPreferred || cursorInMiddle) {
1375
- if (targetTooShort) {
1376
- // Cases 2 and 7
1377
- this.preferredVisualCol = currentVisualCol;
1378
- return targetMaxVisualCol;
1379
- }
1380
-
1381
- // Cases 1 and 6
1382
- this.preferredVisualCol = null;
1383
- return currentVisualCol;
1384
- }
1385
-
1386
- const targetCantFitPreferred = targetMaxVisualCol < this.preferredVisualCol!; // U
1387
- if (targetTooShort || targetCantFitPreferred) {
1388
- // Cases 4 and 5
1389
- return targetMaxVisualCol;
1390
- }
1391
-
1392
- // Case 3
1393
- const result = this.preferredVisualCol!;
1394
- this.preferredVisualCol = null;
1395
- return result;
1396
- }
1397
-
1398
- private moveToLineStart(): void {
1399
- this.lastAction = null;
1400
- this.setCursorCol(0);
1401
- }
1402
-
1403
- private moveToLineEnd(): void {
1404
- this.lastAction = null;
1405
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1406
- this.setCursorCol(currentLine.length);
1407
- }
1408
-
1409
- private deleteToStartOfLine(): void {
1410
- this.historyIndex = -1; // Exit history browsing mode
1411
-
1412
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1413
-
1414
- if (this.state.cursorCol > 0) {
1415
- this.pushUndoSnapshot();
1416
-
1417
- // Calculate text to be deleted and save to kill ring (backward deletion = prepend)
1418
- const deletedText = currentLine.slice(0, this.state.cursorCol);
1419
- this.killRing.push(deletedText, { prepend: true, accumulate: this.lastAction === "kill" });
1420
- this.lastAction = "kill";
1421
-
1422
- // Delete from start of line up to cursor
1423
- this.state.lines[this.state.cursorLine] = currentLine.slice(this.state.cursorCol);
1424
- this.setCursorCol(0);
1425
- } else if (this.state.cursorLine > 0) {
1426
- this.pushUndoSnapshot();
1427
-
1428
- // At start of line - merge with previous line, treating newline as deleted text
1429
- this.killRing.push("\n", { prepend: true, accumulate: this.lastAction === "kill" });
1430
- this.lastAction = "kill";
1431
-
1432
- const previousLine = this.state.lines[this.state.cursorLine - 1] || "";
1433
- this.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;
1434
- this.state.lines.splice(this.state.cursorLine, 1);
1435
- this.state.cursorLine--;
1436
- this.setCursorCol(previousLine.length);
1437
- }
1438
-
1439
- if (this.onChange) {
1440
- this.onChange(this.getText());
1441
- }
1442
- }
1443
-
1444
- private deleteToEndOfLine(): void {
1445
- this.historyIndex = -1; // Exit history browsing mode
1446
-
1447
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1448
-
1449
- if (this.state.cursorCol < currentLine.length) {
1450
- this.pushUndoSnapshot();
1451
-
1452
- // Calculate text to be deleted and save to kill ring (forward deletion = append)
1453
- const deletedText = currentLine.slice(this.state.cursorCol);
1454
- this.killRing.push(deletedText, { prepend: false, accumulate: this.lastAction === "kill" });
1455
- this.lastAction = "kill";
1456
-
1457
- // Delete from cursor to end of line
1458
- this.state.lines[this.state.cursorLine] = currentLine.slice(0, this.state.cursorCol);
1459
- } else if (this.state.cursorLine < this.state.lines.length - 1) {
1460
- this.pushUndoSnapshot();
1461
-
1462
- // At end of line - merge with next line, treating newline as deleted text
1463
- this.killRing.push("\n", { prepend: false, accumulate: this.lastAction === "kill" });
1464
- this.lastAction = "kill";
1465
-
1466
- const nextLine = this.state.lines[this.state.cursorLine + 1] || "";
1467
- this.state.lines[this.state.cursorLine] = currentLine + nextLine;
1468
- this.state.lines.splice(this.state.cursorLine + 1, 1);
1469
- }
1470
-
1471
- if (this.onChange) {
1472
- this.onChange(this.getText());
1473
- }
1474
- }
1475
-
1476
- private deleteWordBackwards(): void {
1477
- this.historyIndex = -1; // Exit history browsing mode
1478
-
1479
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1480
-
1481
- // If at start of line, behave like backspace at column 0 (merge with previous line)
1482
- if (this.state.cursorCol === 0) {
1483
- if (this.state.cursorLine > 0) {
1484
- this.pushUndoSnapshot();
1485
-
1486
- // Treat newline as deleted text (backward deletion = prepend)
1487
- this.killRing.push("\n", { prepend: true, accumulate: this.lastAction === "kill" });
1488
- this.lastAction = "kill";
1489
-
1490
- const previousLine = this.state.lines[this.state.cursorLine - 1] || "";
1491
- this.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;
1492
- this.state.lines.splice(this.state.cursorLine, 1);
1493
- this.state.cursorLine--;
1494
- this.setCursorCol(previousLine.length);
1495
- }
1496
- } else {
1497
- this.pushUndoSnapshot();
1498
-
1499
- // Save lastAction before cursor movement (moveWordBackwards resets it)
1500
- const wasKill = this.lastAction === "kill";
1501
-
1502
- const oldCursorCol = this.state.cursorCol;
1503
- this.moveWordBackwards();
1504
- const deleteFrom = this.state.cursorCol;
1505
- this.setCursorCol(oldCursorCol);
1506
-
1507
- const deletedText = currentLine.slice(deleteFrom, this.state.cursorCol);
1508
- this.killRing.push(deletedText, { prepend: true, accumulate: wasKill });
1509
- this.lastAction = "kill";
1510
-
1511
- this.state.lines[this.state.cursorLine] =
1512
- currentLine.slice(0, deleteFrom) + currentLine.slice(this.state.cursorCol);
1513
- this.setCursorCol(deleteFrom);
1514
- }
1515
-
1516
- if (this.onChange) {
1517
- this.onChange(this.getText());
1518
- }
1519
- }
1520
-
1521
- private deleteWordForward(): void {
1522
- this.historyIndex = -1; // Exit history browsing mode
1523
-
1524
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1525
-
1526
- // If at end of line, merge with next line (delete the newline)
1527
- if (this.state.cursorCol >= currentLine.length) {
1528
- if (this.state.cursorLine < this.state.lines.length - 1) {
1529
- this.pushUndoSnapshot();
1530
-
1531
- // Treat newline as deleted text (forward deletion = append)
1532
- this.killRing.push("\n", { prepend: false, accumulate: this.lastAction === "kill" });
1533
- this.lastAction = "kill";
1534
-
1535
- const nextLine = this.state.lines[this.state.cursorLine + 1] || "";
1536
- this.state.lines[this.state.cursorLine] = currentLine + nextLine;
1537
- this.state.lines.splice(this.state.cursorLine + 1, 1);
1538
- }
1539
- } else {
1540
- this.pushUndoSnapshot();
1541
-
1542
- // Save lastAction before cursor movement (moveWordForwards resets it)
1543
- const wasKill = this.lastAction === "kill";
1544
-
1545
- const oldCursorCol = this.state.cursorCol;
1546
- this.moveWordForwards();
1547
- const deleteTo = this.state.cursorCol;
1548
- this.setCursorCol(oldCursorCol);
1549
-
1550
- const deletedText = currentLine.slice(this.state.cursorCol, deleteTo);
1551
- this.killRing.push(deletedText, { prepend: false, accumulate: wasKill });
1552
- this.lastAction = "kill";
1553
-
1554
- this.state.lines[this.state.cursorLine] =
1555
- currentLine.slice(0, this.state.cursorCol) + currentLine.slice(deleteTo);
1556
- }
1557
-
1558
- if (this.onChange) {
1559
- this.onChange(this.getText());
1560
- }
1561
- }
1562
-
1563
- private handleForwardDelete(): void {
1564
- this.historyIndex = -1; // Exit history browsing mode
1565
- this.lastAction = null;
1566
-
1567
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1568
-
1569
- if (this.state.cursorCol < currentLine.length) {
1570
- this.pushUndoSnapshot();
1571
-
1572
- // Delete grapheme at cursor position (handles emojis, combining characters, etc.)
1573
- const afterCursor = currentLine.slice(this.state.cursorCol);
1574
-
1575
- // Find the first grapheme at cursor
1576
- const graphemes = [...this.segment(afterCursor)];
1577
- const firstGrapheme = graphemes[0];
1578
- const graphemeLength = firstGrapheme ? firstGrapheme.segment.length : 1;
1579
-
1580
- const before = currentLine.slice(0, this.state.cursorCol);
1581
- const after = currentLine.slice(this.state.cursorCol + graphemeLength);
1582
- this.state.lines[this.state.cursorLine] = before + after;
1583
- } else if (this.state.cursorLine < this.state.lines.length - 1) {
1584
- this.pushUndoSnapshot();
1585
-
1586
- // At end of line - merge with next line
1587
- const nextLine = this.state.lines[this.state.cursorLine + 1] || "";
1588
- this.state.lines[this.state.cursorLine] = currentLine + nextLine;
1589
- this.state.lines.splice(this.state.cursorLine + 1, 1);
1590
- }
1591
-
1592
- if (this.onChange) {
1593
- this.onChange(this.getText());
1594
- }
1595
-
1596
- // Update or re-trigger autocomplete after forward delete
1597
- if (this.autocompleteState) {
1598
- this.updateAutocomplete();
1599
- } else {
1600
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1601
- const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
1602
- // Slash command context
1603
- if (this.isInSlashCommandContext(textBeforeCursor)) {
1604
- this.tryTriggerAutocomplete();
1605
- }
1606
- // Symbol-based completion context like @ or #
1607
- else if (textBeforeCursor.match(/(?:^|[\s])[@#][^\s]*$/)) {
1608
- this.tryTriggerAutocomplete();
1609
- }
1610
- }
1611
- }
1612
-
1613
- /**
1614
- * Build a mapping from visual lines to logical positions.
1615
- * Returns an array where each element represents a visual line with:
1616
- * - logicalLine: index into this.state.lines
1617
- * - startCol: starting column in the logical line
1618
- * - length: length of this visual line segment
1619
- */
1620
- private buildVisualLineMap(width: number): Array<{ logicalLine: number; startCol: number; length: number }> {
1621
- const visualLines: Array<{ logicalLine: number; startCol: number; length: number }> = [];
1622
-
1623
- for (let i = 0; i < this.state.lines.length; i++) {
1624
- const line = this.state.lines[i] || "";
1625
- const lineVisWidth = visibleWidth(line);
1626
- if (line.length === 0) {
1627
- // Empty line still takes one visual line
1628
- visualLines.push({ logicalLine: i, startCol: 0, length: 0 });
1629
- } else if (lineVisWidth <= width) {
1630
- visualLines.push({ logicalLine: i, startCol: 0, length: line.length });
1631
- } else {
1632
- // Line needs wrapping - use word-aware wrapping
1633
- const chunks = wordWrapLine(line, width, [...this.segment(line)]);
1634
- for (const chunk of chunks) {
1635
- visualLines.push({
1636
- logicalLine: i,
1637
- startCol: chunk.startIndex,
1638
- length: chunk.endIndex - chunk.startIndex,
1639
- });
1640
- }
1641
- }
1642
- }
1643
-
1644
- return visualLines;
1645
- }
1646
-
1647
- /**
1648
- * Find the visual line index that contains the given logical position.
1649
- */
1650
- private findVisualLineAt(
1651
- visualLines: Array<{ logicalLine: number; startCol: number; length: number }>,
1652
- line: number,
1653
- col: number,
1654
- ): number {
1655
- for (let i = 0; i < visualLines.length; i++) {
1656
- const vl = visualLines[i];
1657
- if (!vl || vl.logicalLine !== line) continue;
1658
- const offset = col - vl.startCol;
1659
- // Cursor is in this segment if it's within range. For the last
1660
- // segment of a logical line, cursor can be at length (end position)
1661
- const isLastSegmentOfLine = i === visualLines.length - 1 || visualLines[i + 1]?.logicalLine !== vl.logicalLine;
1662
- if (offset >= 0 && (offset < vl.length || (isLastSegmentOfLine && offset === vl.length))) {
1663
- return i;
1664
- }
1665
- }
1666
- return visualLines.length - 1;
1667
- }
1668
-
1669
- /**
1670
- * Find the visual line index for the current cursor position.
1671
- */
1672
- private findCurrentVisualLine(
1673
- visualLines: Array<{ logicalLine: number; startCol: number; length: number }>,
1674
- ): number {
1675
- return this.findVisualLineAt(visualLines, this.state.cursorLine, this.state.cursorCol);
1676
- }
1677
-
1678
- private moveCursor(deltaLine: number, deltaCol: number): void {
1679
- this.lastAction = null;
1680
- const visualLines = this.buildVisualLineMap(this.lastWidth);
1681
- const currentVisualLine = this.findCurrentVisualLine(visualLines);
1682
-
1683
- if (deltaLine !== 0) {
1684
- const targetVisualLine = currentVisualLine + deltaLine;
1685
-
1686
- if (targetVisualLine >= 0 && targetVisualLine < visualLines.length) {
1687
- this.moveToVisualLine(visualLines, currentVisualLine, targetVisualLine);
1688
- }
1689
- }
1690
-
1691
- if (deltaCol !== 0) {
1692
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1693
-
1694
- if (deltaCol > 0) {
1695
- // Moving right - move by one grapheme (handles emojis, combining characters, etc.)
1696
- if (this.state.cursorCol < currentLine.length) {
1697
- const afterCursor = currentLine.slice(this.state.cursorCol);
1698
- const graphemes = [...this.segment(afterCursor)];
1699
- const firstGrapheme = graphemes[0];
1700
- this.setCursorCol(this.state.cursorCol + (firstGrapheme ? firstGrapheme.segment.length : 1));
1701
- } else if (this.state.cursorLine < this.state.lines.length - 1) {
1702
- // Wrap to start of next logical line
1703
- this.state.cursorLine++;
1704
- this.setCursorCol(0);
1705
- } else {
1706
- // At end of last line - can't move, but set preferredVisualCol for up/down navigation
1707
- const currentVL = visualLines[currentVisualLine];
1708
- if (currentVL) {
1709
- this.preferredVisualCol = this.state.cursorCol - currentVL.startCol;
1710
- }
1711
- }
1712
- } else {
1713
- // Moving left - move by one grapheme (handles emojis, combining characters, etc.)
1714
- if (this.state.cursorCol > 0) {
1715
- const beforeCursor = currentLine.slice(0, this.state.cursorCol);
1716
- const graphemes = [...this.segment(beforeCursor)];
1717
- const lastGrapheme = graphemes[graphemes.length - 1];
1718
- this.setCursorCol(this.state.cursorCol - (lastGrapheme ? lastGrapheme.segment.length : 1));
1719
- } else if (this.state.cursorLine > 0) {
1720
- // Wrap to end of previous logical line
1721
- this.state.cursorLine--;
1722
- const prevLine = this.state.lines[this.state.cursorLine] || "";
1723
- this.setCursorCol(prevLine.length);
1724
- }
1725
- }
1726
- }
1727
- }
1728
-
1729
- /**
1730
- * Scroll by a page (direction: -1 for up, 1 for down).
1731
- * Moves cursor by the page size while keeping it in bounds.
1732
- */
1733
- private pageScroll(direction: -1 | 1): void {
1734
- this.lastAction = null;
1735
- const terminalRows = this.tui.terminal.rows;
1736
- const pageSize = Math.max(5, Math.floor(terminalRows * 0.3));
1737
-
1738
- const visualLines = this.buildVisualLineMap(this.lastWidth);
1739
- const currentVisualLine = this.findCurrentVisualLine(visualLines);
1740
- const targetVisualLine = Math.max(0, Math.min(visualLines.length - 1, currentVisualLine + direction * pageSize));
1741
-
1742
- this.moveToVisualLine(visualLines, currentVisualLine, targetVisualLine);
1743
- }
1744
-
1745
- private moveWordBackwards(): void {
1746
- this.lastAction = null;
1747
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1748
-
1749
- // If at start of line, move to end of previous line
1750
- if (this.state.cursorCol === 0) {
1751
- if (this.state.cursorLine > 0) {
1752
- this.state.cursorLine--;
1753
- const prevLine = this.state.lines[this.state.cursorLine] || "";
1754
- this.setCursorCol(prevLine.length);
1755
- }
1756
- return;
1757
- }
1758
-
1759
- const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
1760
- const graphemes = [...this.segment(textBeforeCursor)];
1761
- let newCol = this.state.cursorCol;
1762
-
1763
- // Skip trailing whitespace
1764
- while (
1765
- graphemes.length > 0 &&
1766
- !isPasteMarker(graphemes[graphemes.length - 1]?.segment || "") &&
1767
- isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || "")
1768
- ) {
1769
- newCol -= graphemes.pop()?.segment.length || 0;
1770
- }
1771
-
1772
- if (graphemes.length > 0) {
1773
- const lastGrapheme = graphemes[graphemes.length - 1]?.segment || "";
1774
- if (isPasteMarker(lastGrapheme)) {
1775
- // Paste marker is a single atomic word
1776
- newCol -= graphemes.pop()?.segment.length || 0;
1777
- } else if (isPunctuationChar(lastGrapheme)) {
1778
- // Skip punctuation run
1779
- while (
1780
- graphemes.length > 0 &&
1781
- isPunctuationChar(graphemes[graphemes.length - 1]?.segment || "") &&
1782
- !isPasteMarker(graphemes[graphemes.length - 1]?.segment || "")
1783
- ) {
1784
- newCol -= graphemes.pop()?.segment.length || 0;
1785
- }
1786
- } else {
1787
- // Skip word run
1788
- while (
1789
- graphemes.length > 0 &&
1790
- !isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || "") &&
1791
- !isPunctuationChar(graphemes[graphemes.length - 1]?.segment || "") &&
1792
- !isPasteMarker(graphemes[graphemes.length - 1]?.segment || "")
1793
- ) {
1794
- newCol -= graphemes.pop()?.segment.length || 0;
1795
- }
1796
- }
1797
- }
1798
-
1799
- this.setCursorCol(newCol);
1800
- }
1801
-
1802
- /**
1803
- * Yank (paste) the most recent kill ring entry at cursor position.
1804
- */
1805
- private yank(): void {
1806
- if (this.killRing.length === 0) return;
1807
-
1808
- this.pushUndoSnapshot();
1809
-
1810
- const text = this.killRing.peek()!;
1811
- this.insertYankedText(text);
1812
-
1813
- this.lastAction = "yank";
1814
- }
1815
-
1816
- /**
1817
- * Cycle through kill ring (only works immediately after yank or yank-pop).
1818
- * Replaces the last yanked text with the previous entry in the ring.
1819
- */
1820
- private yankPop(): void {
1821
- // Only works if we just yanked and have more than one entry
1822
- if (this.lastAction !== "yank" || this.killRing.length <= 1) return;
1823
-
1824
- this.pushUndoSnapshot();
1825
-
1826
- // Delete the previously yanked text (still at end of ring before rotation)
1827
- this.deleteYankedText();
1828
-
1829
- // Rotate the ring: move end to front
1830
- this.killRing.rotate();
1831
-
1832
- // Insert the new most recent entry (now at end after rotation)
1833
- const text = this.killRing.peek()!;
1834
- this.insertYankedText(text);
1835
-
1836
- this.lastAction = "yank";
1837
- }
1838
-
1839
- /**
1840
- * Insert text at cursor position (used by yank operations).
1841
- */
1842
- private insertYankedText(text: string): void {
1843
- this.historyIndex = -1; // Exit history browsing mode
1844
- const lines = text.split("\n");
1845
-
1846
- if (lines.length === 1) {
1847
- // Single line - insert at cursor
1848
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1849
- const before = currentLine.slice(0, this.state.cursorCol);
1850
- const after = currentLine.slice(this.state.cursorCol);
1851
- this.state.lines[this.state.cursorLine] = before + text + after;
1852
- this.setCursorCol(this.state.cursorCol + text.length);
1853
- } else {
1854
- // Multi-line insert
1855
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1856
- const before = currentLine.slice(0, this.state.cursorCol);
1857
- const after = currentLine.slice(this.state.cursorCol);
1858
-
1859
- // First line merges with text before cursor
1860
- this.state.lines[this.state.cursorLine] = before + (lines[0] || "");
1861
-
1862
- // Insert middle lines
1863
- for (let i = 1; i < lines.length - 1; i++) {
1864
- this.state.lines.splice(this.state.cursorLine + i, 0, lines[i] || "");
1865
- }
1866
-
1867
- // Last line merges with text after cursor
1868
- const lastLineIndex = this.state.cursorLine + lines.length - 1;
1869
- this.state.lines.splice(lastLineIndex, 0, (lines[lines.length - 1] || "") + after);
1870
-
1871
- // Update cursor position
1872
- this.state.cursorLine = lastLineIndex;
1873
- this.setCursorCol((lines[lines.length - 1] || "").length);
1874
- }
1875
-
1876
- if (this.onChange) {
1877
- this.onChange(this.getText());
1878
- }
1879
- }
1880
-
1881
- /**
1882
- * Delete the previously yanked text (used by yank-pop).
1883
- * The yanked text is derived from killRing[end] since it hasn't been rotated yet.
1884
- */
1885
- private deleteYankedText(): void {
1886
- const yankedText = this.killRing.peek();
1887
- if (!yankedText) return;
1888
-
1889
- const yankLines = yankedText.split("\n");
1890
-
1891
- if (yankLines.length === 1) {
1892
- // Single line - delete backward from cursor
1893
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1894
- const deleteLen = yankedText.length;
1895
- const before = currentLine.slice(0, this.state.cursorCol - deleteLen);
1896
- const after = currentLine.slice(this.state.cursorCol);
1897
- this.state.lines[this.state.cursorLine] = before + after;
1898
- this.setCursorCol(this.state.cursorCol - deleteLen);
1899
- } else {
1900
- // Multi-line delete - cursor is at end of last yanked line
1901
- const startLine = this.state.cursorLine - (yankLines.length - 1);
1902
- const startCol = (this.state.lines[startLine] || "").length - (yankLines[0] || "").length;
1903
-
1904
- // Get text after cursor on current line
1905
- const afterCursor = (this.state.lines[this.state.cursorLine] || "").slice(this.state.cursorCol);
1906
-
1907
- // Get text before yank start position
1908
- const beforeYank = (this.state.lines[startLine] || "").slice(0, startCol);
1909
-
1910
- // Remove all lines from startLine to cursorLine and replace with merged line
1911
- this.state.lines.splice(startLine, yankLines.length, beforeYank + afterCursor);
1912
-
1913
- // Update cursor
1914
- this.state.cursorLine = startLine;
1915
- this.setCursorCol(startCol);
1916
- }
1917
-
1918
- if (this.onChange) {
1919
- this.onChange(this.getText());
1920
- }
1921
- }
1922
-
1923
- private pushUndoSnapshot(): void {
1924
- this.undoStack.push(this.state);
1925
- }
1926
-
1927
- private undo(): void {
1928
- this.historyIndex = -1; // Exit history browsing mode
1929
- const snapshot = this.undoStack.pop();
1930
- if (!snapshot) return;
1931
- Object.assign(this.state, snapshot);
1932
- this.lastAction = null;
1933
- this.preferredVisualCol = null;
1934
- if (this.onChange) {
1935
- this.onChange(this.getText());
1936
- }
1937
- }
1938
-
1939
- /**
1940
- * Jump to the first occurrence of a character in the specified direction.
1941
- * Multi-line search. Case-sensitive. Skips the current cursor position.
1942
- */
1943
- private jumpToChar(char: string, direction: "forward" | "backward"): void {
1944
- this.lastAction = null;
1945
- const isForward = direction === "forward";
1946
- const lines = this.state.lines;
1947
-
1948
- const end = isForward ? lines.length : -1;
1949
- const step = isForward ? 1 : -1;
1950
-
1951
- for (let lineIdx = this.state.cursorLine; lineIdx !== end; lineIdx += step) {
1952
- const line = lines[lineIdx] || "";
1953
- const isCurrentLine = lineIdx === this.state.cursorLine;
1954
-
1955
- // Current line: start after/before cursor; other lines: search full line
1956
- const searchFrom = isCurrentLine
1957
- ? isForward
1958
- ? this.state.cursorCol + 1
1959
- : this.state.cursorCol - 1
1960
- : undefined;
1961
-
1962
- const idx = isForward ? line.indexOf(char, searchFrom) : line.lastIndexOf(char, searchFrom);
1963
-
1964
- if (idx !== -1) {
1965
- this.state.cursorLine = lineIdx;
1966
- this.setCursorCol(idx);
1967
- return;
1968
- }
1969
- }
1970
- // No match found - cursor stays in place
1971
- }
1972
-
1973
- private moveWordForwards(): void {
1974
- this.lastAction = null;
1975
- const currentLine = this.state.lines[this.state.cursorLine] || "";
1976
-
1977
- // If at end of line, move to start of next line
1978
- if (this.state.cursorCol >= currentLine.length) {
1979
- if (this.state.cursorLine < this.state.lines.length - 1) {
1980
- this.state.cursorLine++;
1981
- this.setCursorCol(0);
1982
- }
1983
- return;
1984
- }
1985
-
1986
- const textAfterCursor = currentLine.slice(this.state.cursorCol);
1987
- const segments = this.segment(textAfterCursor);
1988
- const iterator = segments[Symbol.iterator]();
1989
- let next = iterator.next();
1990
- let newCol = this.state.cursorCol;
1991
-
1992
- // Skip leading whitespace
1993
- while (!next.done && !isPasteMarker(next.value.segment) && isWhitespaceChar(next.value.segment)) {
1994
- newCol += next.value.segment.length;
1995
- next = iterator.next();
1996
- }
1997
-
1998
- if (!next.done) {
1999
- const firstGrapheme = next.value.segment;
2000
- if (isPasteMarker(firstGrapheme)) {
2001
- // Paste marker is a single atomic word
2002
- newCol += firstGrapheme.length;
2003
- } else if (isPunctuationChar(firstGrapheme)) {
2004
- // Skip punctuation run
2005
- while (!next.done && isPunctuationChar(next.value.segment) && !isPasteMarker(next.value.segment)) {
2006
- newCol += next.value.segment.length;
2007
- next = iterator.next();
2008
- }
2009
- } else {
2010
- // Skip word run
2011
- while (
2012
- !next.done &&
2013
- !isWhitespaceChar(next.value.segment) &&
2014
- !isPunctuationChar(next.value.segment) &&
2015
- !isPasteMarker(next.value.segment)
2016
- ) {
2017
- newCol += next.value.segment.length;
2018
- next = iterator.next();
2019
- }
2020
- }
2021
- }
2022
-
2023
- this.setCursorCol(newCol);
2024
- }
2025
-
2026
- // Slash menu only allowed on the first line of the editor
2027
- private isSlashMenuAllowed(): boolean {
2028
- return this.state.cursorLine === 0;
2029
- }
2030
-
2031
- // Helper method to check if cursor is at start of message (for slash command detection)
2032
- private isAtStartOfMessage(): boolean {
2033
- if (!this.isSlashMenuAllowed()) return false;
2034
- const currentLine = this.state.lines[this.state.cursorLine] || "";
2035
- const beforeCursor = currentLine.slice(0, this.state.cursorCol);
2036
- return beforeCursor.trim() === "" || beforeCursor.trim() === "/";
2037
- }
2038
-
2039
- private isInSlashCommandContext(textBeforeCursor: string): boolean {
2040
- return this.isSlashMenuAllowed() && textBeforeCursor.trimStart().startsWith("/");
2041
- }
2042
-
2043
- // Autocomplete methods
2044
- /**
2045
- * Find the best autocomplete item index for the given prefix.
2046
- * Returns -1 if no match is found.
2047
- *
2048
- * Match priority:
2049
- * 1. Exact match (prefix === item.value) -> always selected
2050
- * 2. Prefix match -> first item whose value starts with prefix
2051
- * 3. No match -> -1 (keep default highlight)
2052
- *
2053
- * Matching is case-sensitive and checks item.value only.
2054
- */
2055
- private getBestAutocompleteMatchIndex(items: Array<{ value: string; label: string }>, prefix: string): number {
2056
- if (!prefix) return -1;
2057
-
2058
- let firstPrefixIndex = -1;
2059
-
2060
- for (let i = 0; i < items.length; i++) {
2061
- const value = items[i]!.value;
2062
- if (value === prefix) {
2063
- return i; // Exact match always wins
2064
- }
2065
- if (firstPrefixIndex === -1 && value.startsWith(prefix)) {
2066
- firstPrefixIndex = i;
2067
- }
2068
- }
2069
-
2070
- return firstPrefixIndex;
2071
- }
2072
-
2073
- private createAutocompleteList(
2074
- prefix: string,
2075
- items: Array<{ value: string; label: string; description?: string }>,
2076
- ): SelectList {
2077
- const layout = prefix.startsWith("/") ? SLASH_COMMAND_SELECT_LIST_LAYOUT : undefined;
2078
- return new SelectList(items, this.autocompleteMaxVisible, this.theme.selectList, layout);
2079
- }
2080
-
2081
- private tryTriggerAutocomplete(explicitTab: boolean = false): void {
2082
- this.requestAutocomplete({ force: false, explicitTab });
2083
- }
2084
-
2085
- private handleTabCompletion(): void {
2086
- if (!this.autocompleteProvider) return;
2087
-
2088
- const currentLine = this.state.lines[this.state.cursorLine] || "";
2089
- const beforeCursor = currentLine.slice(0, this.state.cursorCol);
2090
-
2091
- if (this.isInSlashCommandContext(beforeCursor) && !beforeCursor.trimStart().includes(" ")) {
2092
- this.handleSlashCommandCompletion();
2093
- } else {
2094
- this.forceFileAutocomplete(true);
2095
- }
2096
- }
2097
-
2098
- private handleSlashCommandCompletion(): void {
2099
- this.requestAutocomplete({ force: false, explicitTab: true });
2100
- }
2101
-
2102
- private forceFileAutocomplete(explicitTab: boolean = false): void {
2103
- this.requestAutocomplete({ force: true, explicitTab });
2104
- }
2105
-
2106
- private requestAutocomplete(options: { force: boolean; explicitTab: boolean }): void {
2107
- if (!this.autocompleteProvider) return;
2108
-
2109
- if (options.force) {
2110
- const shouldTrigger =
2111
- !this.autocompleteProvider.shouldTriggerFileCompletion ||
2112
- this.autocompleteProvider.shouldTriggerFileCompletion(
2113
- this.state.lines,
2114
- this.state.cursorLine,
2115
- this.state.cursorCol,
2116
- );
2117
- if (!shouldTrigger) {
2118
- return;
2119
- }
2120
- }
2121
-
2122
- this.cancelAutocompleteRequest();
2123
- const startToken = ++this.autocompleteStartToken;
2124
-
2125
- const debounceMs = this.getAutocompleteDebounceMs(options);
2126
- if (debounceMs > 0) {
2127
- this.autocompleteDebounceTimer = setTimeout(() => {
2128
- this.autocompleteDebounceTimer = undefined;
2129
- void this.startAutocompleteRequest(startToken, options);
2130
- }, debounceMs);
2131
- return;
2132
- }
2133
-
2134
- void this.startAutocompleteRequest(startToken, options);
2135
- }
2136
-
2137
- private async startAutocompleteRequest(
2138
- startToken: number,
2139
- options: { force: boolean; explicitTab: boolean },
2140
- ): Promise<void> {
2141
- const previousTask = this.autocompleteRequestTask;
2142
- this.autocompleteRequestTask = (async () => {
2143
- await previousTask;
2144
- if (startToken !== this.autocompleteStartToken || !this.autocompleteProvider) {
2145
- return;
2146
- }
2147
-
2148
- const controller = new AbortController();
2149
- this.autocompleteAbort = controller;
2150
- const requestId = ++this.autocompleteRequestId;
2151
- const snapshotText = this.getText();
2152
- const snapshotLine = this.state.cursorLine;
2153
- const snapshotCol = this.state.cursorCol;
2154
-
2155
- await this.runAutocompleteRequest(requestId, controller, snapshotText, snapshotLine, snapshotCol, options);
2156
- })();
2157
- await this.autocompleteRequestTask;
2158
- }
2159
-
2160
- private getAutocompleteDebounceMs(options: { force: boolean; explicitTab: boolean }): number {
2161
- if (options.explicitTab || options.force) {
2162
- return 0;
2163
- }
2164
-
2165
- const currentLine = this.state.lines[this.state.cursorLine] || "";
2166
- const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
2167
- const isSymbolAutocompleteContext = /(?:^|[ \t])(?:@(?:"[^"]*|[^\s]*)|#[^\s]*)$/.test(textBeforeCursor);
2168
- return isSymbolAutocompleteContext ? ATTACHMENT_AUTOCOMPLETE_DEBOUNCE_MS : 0;
2169
- }
2170
-
2171
- private async runAutocompleteRequest(
2172
- requestId: number,
2173
- controller: AbortController,
2174
- snapshotText: string,
2175
- snapshotLine: number,
2176
- snapshotCol: number,
2177
- options: { force: boolean; explicitTab: boolean },
2178
- ): Promise<void> {
2179
- if (!this.autocompleteProvider) return;
2180
-
2181
- const suggestions = await this.autocompleteProvider.getSuggestions(
2182
- this.state.lines,
2183
- this.state.cursorLine,
2184
- this.state.cursorCol,
2185
- { signal: controller.signal, force: options.force },
2186
- );
2187
-
2188
- if (!this.isAutocompleteRequestCurrent(requestId, controller, snapshotText, snapshotLine, snapshotCol)) {
2189
- return;
2190
- }
2191
-
2192
- this.autocompleteAbort = undefined;
2193
-
2194
- if (!suggestions || !Array.isArray(suggestions.items) || suggestions.items.length === 0) {
2195
- this.cancelAutocomplete();
2196
- this.tui.requestRender();
2197
- return;
2198
- }
2199
-
2200
- if (options.force && options.explicitTab && suggestions.items.length === 1) {
2201
- const item = suggestions.items[0]!;
2202
- this.pushUndoSnapshot();
2203
- this.lastAction = null;
2204
- const result = this.autocompleteProvider.applyCompletion(
2205
- this.state.lines,
2206
- this.state.cursorLine,
2207
- this.state.cursorCol,
2208
- item,
2209
- suggestions.prefix,
2210
- );
2211
- this.state.lines = result.lines;
2212
- this.state.cursorLine = result.cursorLine;
2213
- this.setCursorCol(result.cursorCol);
2214
- if (this.onChange) this.onChange(this.getText());
2215
- this.tui.requestRender();
2216
- return;
2217
- }
2218
-
2219
- this.applyAutocompleteSuggestions(suggestions, options.force ? "force" : "regular");
2220
- this.tui.requestRender();
2221
- }
2222
-
2223
- private isAutocompleteRequestCurrent(
2224
- requestId: number,
2225
- controller: AbortController,
2226
- snapshotText: string,
2227
- snapshotLine: number,
2228
- snapshotCol: number,
2229
- ): boolean {
2230
- return (
2231
- !controller.signal.aborted &&
2232
- requestId === this.autocompleteRequestId &&
2233
- this.getText() === snapshotText &&
2234
- this.state.cursorLine === snapshotLine &&
2235
- this.state.cursorCol === snapshotCol
2236
- );
2237
- }
2238
-
2239
- private applyAutocompleteSuggestions(suggestions: AutocompleteSuggestions, state: "regular" | "force"): void {
2240
- this.autocompletePrefix = suggestions.prefix;
2241
- this.autocompleteList = this.createAutocompleteList(suggestions.prefix, suggestions.items);
2242
-
2243
- const bestMatchIndex = this.getBestAutocompleteMatchIndex(suggestions.items, suggestions.prefix);
2244
- if (bestMatchIndex >= 0) {
2245
- this.autocompleteList.setSelectedIndex(bestMatchIndex);
2246
- }
2247
-
2248
- this.autocompleteState = state;
2249
- }
2250
-
2251
- private cancelAutocompleteRequest(): void {
2252
- this.autocompleteStartToken += 1;
2253
- if (this.autocompleteDebounceTimer) {
2254
- clearTimeout(this.autocompleteDebounceTimer);
2255
- this.autocompleteDebounceTimer = undefined;
2256
- }
2257
- this.autocompleteAbort?.abort();
2258
- this.autocompleteAbort = undefined;
2259
- }
2260
-
2261
- private clearAutocompleteUi(): void {
2262
- this.autocompleteState = null;
2263
- this.autocompleteList = undefined;
2264
- this.autocompletePrefix = "";
2265
- }
2266
-
2267
- private cancelAutocomplete(): void {
2268
- this.cancelAutocompleteRequest();
2269
- this.clearAutocompleteUi();
2270
- }
2271
-
2272
- public isShowingAutocomplete(): boolean {
2273
- return this.autocompleteState !== null;
2274
- }
2275
-
2276
- private updateAutocomplete(): void {
2277
- if (!this.autocompleteState || !this.autocompleteProvider) return;
2278
- this.requestAutocomplete({ force: this.autocompleteState === "force", explicitTab: false });
2279
- }
2280
- }