@selesai/code 0.9.14 → 0.10.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 (263) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +79 -29
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +3 -4
  6. package/dist/extensions/pi-zentui/.editorconfig +12 -0
  7. package/dist/extensions/pi-zentui/.github/CODEOWNERS +1 -0
  8. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/bug_report.yml +110 -0
  9. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config.yml +8 -0
  10. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config_question.yml +56 -0
  11. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
  12. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/rendering_issue.yml +106 -0
  13. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/runtime_detection.yml +103 -0
  14. package/dist/extensions/pi-zentui/.github/dependabot.yml +47 -0
  15. package/dist/extensions/pi-zentui/.github/pull_request_template.md +34 -0
  16. package/dist/extensions/pi-zentui/.github/release.yml +18 -0
  17. package/dist/extensions/pi-zentui/.github/workflows/ci.yml +79 -0
  18. package/dist/extensions/pi-zentui/.github/workflows/publish.yml +63 -0
  19. package/dist/extensions/pi-zentui/AGENTS.md +107 -0
  20. package/dist/extensions/pi-zentui/CONTRIBUTING.md +68 -0
  21. package/dist/extensions/pi-zentui/LICENSE +21 -0
  22. package/dist/extensions/pi-zentui/README.md +188 -0
  23. package/dist/extensions/pi-zentui/SECURITY.md +18 -0
  24. package/dist/extensions/pi-zentui/assets/main-cover.png +0 -0
  25. package/dist/extensions/pi-zentui/assets/screenshots/editor-accent-rail.png +0 -0
  26. package/dist/extensions/pi-zentui/assets/screenshots/editor-minimalist.png +0 -0
  27. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode-copy-friendly.png +0 -0
  28. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode.png +0 -0
  29. package/dist/extensions/pi-zentui/assets/screenshots/user-message-compact.png +0 -0
  30. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed-copy-friendly.png +0 -0
  31. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed.png +0 -0
  32. package/dist/extensions/pi-zentui/assets/screenshots/user-message-labeled.png +0 -0
  33. package/dist/extensions/pi-zentui/assets/zentui.png +0 -0
  34. package/dist/extensions/pi-zentui/biome.json +31 -0
  35. package/dist/extensions/pi-zentui/docs/configuration.md +509 -0
  36. package/dist/extensions/pi-zentui/docs/footer-format.md +162 -0
  37. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +99 -0
  38. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +457 -0
  39. package/dist/extensions/pi-zentui/extensions/zentui/completion-menu.ts +157 -0
  40. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +2100 -0
  41. package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +321 -0
  42. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +73 -0
  43. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +89 -0
  44. package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +364 -0
  45. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +122 -0
  46. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +812 -0
  47. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +568 -0
  48. package/dist/extensions/pi-zentui/extensions/zentui/git.ts +384 -0
  49. package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +252 -0
  50. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +1406 -0
  51. package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +1049 -0
  52. package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +75 -0
  53. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +463 -0
  54. package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +651 -0
  55. package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +120 -0
  56. package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +59 -0
  57. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +166 -0
  58. package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +98 -0
  59. package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +841 -0
  60. package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +93 -0
  61. package/dist/extensions/pi-zentui/extensions/zentui/session-lifecycle.ts +60 -0
  62. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1796 -0
  63. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +128 -0
  64. package/dist/extensions/pi-zentui/extensions/zentui/state.ts +94 -0
  65. package/dist/extensions/pi-zentui/extensions/zentui/style.ts +424 -0
  66. package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +82 -0
  67. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +1290 -0
  68. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +248 -0
  69. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +215 -0
  70. package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +206 -0
  71. package/dist/extensions/pi-zentui/extensions/zentui/working-line-messages.ts +24 -0
  72. package/dist/extensions/pi-zentui/extensions/zentui/working-line-spinners.ts +54 -0
  73. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +1433 -0
  74. package/dist/extensions/pi-zentui/package-lock.json +5311 -0
  75. package/dist/extensions/pi-zentui/package.json +60 -0
  76. package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +232 -0
  77. package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +669 -0
  78. package/dist/extensions/pi-zentui/test/completion-menu.test.ts +265 -0
  79. package/dist/extensions/pi-zentui/test/config.test.ts +3119 -0
  80. package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +405 -0
  81. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +260 -0
  82. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +1286 -0
  83. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +7196 -0
  84. package/dist/extensions/pi-zentui/test/extension-status.test.ts +182 -0
  85. package/dist/extensions/pi-zentui/test/fixtures/starship-nerd-font-symbols.toml +172 -0
  86. package/dist/extensions/pi-zentui/test/footer-format.test.ts +431 -0
  87. package/dist/extensions/pi-zentui/test/footer-layout.test.ts +113 -0
  88. package/dist/extensions/pi-zentui/test/footer-repository-path.test.ts +123 -0
  89. package/dist/extensions/pi-zentui/test/format.test.ts +865 -0
  90. package/dist/extensions/pi-zentui/test/git.test.ts +212 -0
  91. package/dist/extensions/pi-zentui/test/icons.test.ts +69 -0
  92. package/dist/extensions/pi-zentui/test/interaction-summary.test.ts +1402 -0
  93. package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +378 -0
  94. package/dist/extensions/pi-zentui/test/live-context.test.ts +139 -0
  95. package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +811 -0
  96. package/dist/extensions/pi-zentui/test/package-version.test.ts +321 -0
  97. package/dist/extensions/pi-zentui/test/project-refresh.test.ts +250 -0
  98. package/dist/extensions/pi-zentui/test/project-state.test.ts +159 -0
  99. package/dist/extensions/pi-zentui/test/repository-root.test.ts +108 -0
  100. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +825 -0
  101. package/dist/extensions/pi-zentui/test/runtime.test.ts +183 -0
  102. package/dist/extensions/pi-zentui/test/session-lifecycle.test.ts +40 -0
  103. package/dist/extensions/pi-zentui/test/settings-command.test.ts +1381 -0
  104. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +347 -0
  105. package/dist/extensions/pi-zentui/test/state.test.ts +84 -0
  106. package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +276 -0
  107. package/dist/extensions/pi-zentui/test/telemetry.test.ts +105 -0
  108. package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +327 -0
  109. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +164 -0
  110. package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +292 -0
  111. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +730 -0
  112. package/dist/extensions/pi-zentui/test/working-line-messages.test.ts +34 -0
  113. package/dist/extensions/pi-zentui/test/working-line.test.ts +1990 -0
  114. package/dist/extensions/pi-zentui/tsconfig.json +16 -0
  115. package/dist/extensions/tps.test.ts +38 -0
  116. package/dist/extensions/{pi-powerline-footer/tps.ts → tps.ts} +4 -0
  117. package/dist/skills/design-references/SKILL.md +31 -0
  118. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  119. package/dist/themes/adventure.json +84 -0
  120. package/dist/themes/adwaita-dark.json +84 -0
  121. package/dist/themes/arcoiris.json +84 -0
  122. package/dist/themes/arthur.json +84 -0
  123. package/dist/themes/atom.json +84 -0
  124. package/dist/themes/aura.json +84 -0
  125. package/dist/themes/black-metal-bathory.json +84 -0
  126. package/dist/themes/black-metal-burzum.json +84 -0
  127. package/dist/themes/black-metal-khold.json +84 -0
  128. package/dist/themes/box.json +84 -0
  129. package/dist/themes/brogrammer.json +84 -0
  130. package/dist/themes/carbonfox.json +84 -0
  131. package/dist/themes/catppuccin-mocha.json +84 -0
  132. package/dist/themes/citruszest.json +84 -0
  133. package/dist/themes/cursor-dark.json +84 -0
  134. package/dist/themes/cutie-pro.json +84 -0
  135. package/dist/themes/dark-modern.json +84 -0
  136. package/dist/themes/dark-pastel.json +84 -0
  137. package/dist/themes/dimmed-monokai.json +84 -0
  138. package/dist/themes/doom-peacock.json +84 -0
  139. package/dist/themes/dracula-plus.json +84 -0
  140. package/dist/themes/dracula.json +80 -0
  141. package/dist/themes/earthsong.json +84 -0
  142. package/dist/themes/everforest-dark-hard.json +84 -0
  143. package/dist/themes/everforest-dark.json +80 -0
  144. package/dist/themes/fahrenheit.json +84 -0
  145. package/dist/themes/flatland.json +84 -0
  146. package/dist/themes/flexoki-dark.json +84 -0
  147. package/dist/themes/front-end-delight.json +84 -0
  148. package/dist/themes/fun-forrest.json +84 -0
  149. package/dist/themes/galizur.json +84 -0
  150. package/dist/themes/github-dark-colorblind.json +84 -0
  151. package/dist/themes/github-dark-high-contrast.json +84 -0
  152. package/dist/themes/glacier.json +84 -0
  153. package/dist/themes/gruber-darker.json +84 -0
  154. package/dist/themes/gruvbox-dark-hard.json +84 -0
  155. package/dist/themes/gruvbox-dark.json +84 -0
  156. package/dist/themes/gruvbox-material.json +84 -0
  157. package/dist/themes/guezwhoz.json +84 -0
  158. package/dist/themes/hacktober.json +84 -0
  159. package/dist/themes/hardcore.json +84 -0
  160. package/dist/themes/havn-skumring.json +84 -0
  161. package/dist/themes/ic-orange-ppl.json +84 -0
  162. package/dist/themes/iterm2-smoooooth.json +84 -0
  163. package/dist/themes/iterm2-tango-dark.json +84 -0
  164. package/dist/themes/japanesque.json +84 -0
  165. package/dist/themes/jellybeans.json +84 -0
  166. package/dist/themes/kanagawa-wave.json +84 -0
  167. package/dist/themes/kurokula.json +84 -0
  168. package/dist/themes/later-this-evening.json +84 -0
  169. package/dist/themes/lovelace.json +84 -0
  170. package/dist/themes/material-darker.json +84 -0
  171. package/dist/themes/matte-black.json +84 -0
  172. package/dist/themes/mellow.json +84 -0
  173. package/dist/themes/miasma.json +84 -0
  174. package/dist/themes/nord.json +80 -0
  175. package/dist/themes/nvim-dark.json +84 -0
  176. package/dist/themes/one-dark.json +80 -0
  177. package/dist/themes/popping-and-locking.json +84 -0
  178. package/dist/themes/rose-pine.json +80 -0
  179. package/dist/themes/sea-shells.json +84 -0
  180. package/dist/themes/sleepy-hollow.json +84 -0
  181. package/dist/themes/smyck.json +84 -0
  182. package/dist/themes/solarized-dark.json +80 -0
  183. package/dist/themes/tokyo-night.json +80 -0
  184. package/dist/themes/tomorrow-night-bright.json +84 -0
  185. package/dist/themes/tomorrow-night-burns.json +84 -0
  186. package/dist/themes/tomorrow-night.json +84 -0
  187. package/dist/themes/twilight.json +84 -0
  188. package/dist/themes/vague.json +84 -0
  189. package/dist/themes/vesper.json +84 -0
  190. package/dist/themes/xcode-dark-hc.json +84 -0
  191. package/docs/custom-provider.md +0 -1
  192. package/docs/plans/auto-model-routing-extension.md +490 -0
  193. package/package.json +3 -3
  194. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  195. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  196. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  197. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  198. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  199. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  200. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  201. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  202. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  203. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  204. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  205. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  206. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  207. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  208. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  209. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  210. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  211. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  212. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  213. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  214. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  215. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  216. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  217. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  218. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  219. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  220. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  221. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  222. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  223. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  224. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  225. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  226. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  227. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  228. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  229. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  230. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  231. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  232. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  233. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  234. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  235. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  236. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  237. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  238. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  239. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  240. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  241. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  242. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  243. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  244. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  245. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  246. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  247. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  248. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  249. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  250. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  251. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  252. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  253. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  254. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  255. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  256. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  257. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  258. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  259. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  260. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  261. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  262. package/dist/extensions/preview-tools-disabled.ts +0 -11
  263. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -0,0 +1,75 @@
1
+ import type { AssistantMessage, Usage } from "@earendil-works/pi-ai";
2
+ import type { SessionLifecycle } from "./session-lifecycle";
3
+
4
+ export type LiveContextOverride = {
5
+ tokens: number;
6
+ };
7
+
8
+ function usageComponent(value: unknown): number {
9
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
10
+ }
11
+
12
+ export function calculateLiveContextTokens(usage: Usage | undefined): number | undefined {
13
+ if (!usage) return undefined;
14
+ const totalTokens = usageComponent(usage.totalTokens);
15
+ if (totalTokens > 0) return totalTokens;
16
+ const calculated =
17
+ usageComponent(usage.input) +
18
+ usageComponent(usage.output) +
19
+ usageComponent(usage.cacheRead) +
20
+ usageComponent(usage.cacheWrite);
21
+ return calculated > 0 ? calculated : undefined;
22
+ }
23
+
24
+ export function liveContextFromMessage(message: unknown): LiveContextOverride | undefined {
25
+ if (!message || typeof message !== "object") return undefined;
26
+ const assistant = message as Partial<AssistantMessage>;
27
+ if (assistant.role !== "assistant") return undefined;
28
+ if (assistant.stopReason === "error" || assistant.stopReason === "aborted") return undefined;
29
+ const tokens = calculateLiveContextTokens(assistant.usage);
30
+ return tokens === undefined ? undefined : { tokens };
31
+ }
32
+
33
+ export class LiveContextController {
34
+ private readonly lifecycle: SessionLifecycle;
35
+ private readonly requestRender: () => void;
36
+ private override: LiveContextOverride | undefined;
37
+ private cancelScheduledRender: (() => void) | undefined;
38
+ private scheduledGeneration: number | undefined;
39
+
40
+ constructor(lifecycle: SessionLifecycle, requestRender: () => void) {
41
+ this.lifecycle = lifecycle;
42
+ this.requestRender = requestRender;
43
+ }
44
+
45
+ get(): LiveContextOverride | undefined {
46
+ return this.override;
47
+ }
48
+
49
+ update(message: unknown): boolean {
50
+ const next = liveContextFromMessage(message);
51
+ if (!next || !this.lifecycle.isCurrent()) return false;
52
+ this.override = next;
53
+ const generation = this.lifecycle.currentGeneration();
54
+ if (this.cancelScheduledRender && this.scheduledGeneration !== generation) {
55
+ this.cancelScheduledRender = undefined;
56
+ this.scheduledGeneration = undefined;
57
+ }
58
+ if (!this.cancelScheduledRender) {
59
+ this.scheduledGeneration = generation;
60
+ this.cancelScheduledRender = this.lifecycle.defer(() => {
61
+ this.cancelScheduledRender = undefined;
62
+ this.scheduledGeneration = undefined;
63
+ if (this.override) this.requestRender();
64
+ }, 250);
65
+ }
66
+ return true;
67
+ }
68
+
69
+ clear(): void {
70
+ this.override = undefined;
71
+ this.cancelScheduledRender?.();
72
+ this.cancelScheduledRender = undefined;
73
+ this.scheduledGeneration = undefined;
74
+ }
75
+ }
@@ -0,0 +1,463 @@
1
+ import { basename, isAbsolute, relative, sep } from "node:path";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
3
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
4
+ import type { ZentuiConfig } from "./config";
5
+ import { sanitizeEditorMetadataText } from "./editor-metadata-format";
6
+ import {
7
+ buildContextGauge,
8
+ contextColorTier,
9
+ formatCount,
10
+ formatCwdLabel,
11
+ formatElapsedDuration,
12
+ } from "./format";
13
+
14
+ export { formatElapsedDuration } from "./format";
15
+
16
+ import {
17
+ EDITOR_ACCENT_FALLBACK,
18
+ EDITOR_BORDER_FALLBACK,
19
+ renderStyleForSource,
20
+ renderStyleForSourceOrFallback,
21
+ safeThemeFg,
22
+ } from "./style";
23
+
24
+ const MINIMALIST_MODEL_FALLBACK = {
25
+ theme: "syntaxKeyword",
26
+ terminal: "bold purple",
27
+ };
28
+ const MINIMALIST_THINKING_FALLBACK = { theme: "warning", terminal: "bold yellow" };
29
+ const MINIMALIST_ADAPTIVE_TERMINAL_THINKING_FALLBACKS: Record<string, string> = {
30
+ minimal: "bright-black",
31
+ low: "blue",
32
+ medium: "cyan",
33
+ high: "yellow",
34
+ xhigh: "red",
35
+ max: "bright-red",
36
+ };
37
+ const MINIMALIST_BRANCH_FALLBACK = { theme: "bold syntaxKeyword", terminal: "bold blue" };
38
+
39
+ export type MinimalistEditorMetadata = {
40
+ cwd: string;
41
+ projectRoot?: string;
42
+ branch?: string;
43
+ dirty?: boolean;
44
+ ahead?: number;
45
+ behind?: number;
46
+ costLabel?: string;
47
+ modelLabel?: string;
48
+ thinkingLevel?: string;
49
+ contextPercent?: number;
50
+ contextWindow?: number;
51
+ sessionName?: string;
52
+ agentDurationMs?: number;
53
+ agentActive?: boolean;
54
+ };
55
+
56
+ export type MinimalistFrameOptions = {
57
+ width: number;
58
+ editorLines: string[];
59
+ autocompleteLines?: string[];
60
+ viewport?: {
61
+ above?: string;
62
+ below?: string;
63
+ };
64
+ inputText: string;
65
+ metadata: MinimalistEditorMetadata;
66
+ uiTheme: Theme;
67
+ config: ZentuiConfig;
68
+ borderColor?: (text: string) => string;
69
+ };
70
+
71
+ function fillLine(content: string, width: number): string {
72
+ const truncated = truncateToWidth(content, Math.max(0, width), "");
73
+ return `${truncated}${" ".repeat(Math.max(0, width - visibleWidth(truncated)))}`;
74
+ }
75
+
76
+ function clampLines(lines: string[], width: number): string[] {
77
+ return lines.map((line) => truncateToWidth(line, Math.max(0, width), ""));
78
+ }
79
+
80
+ export function renderFramedAutocompleteRows({
81
+ width,
82
+ lines,
83
+ renderBorder,
84
+ }: {
85
+ width: number;
86
+ lines: string[];
87
+ renderBorder: (text: string) => string;
88
+ }): string[] {
89
+ if (width <= 4 || lines.length === 0) return clampLines(lines, width);
90
+ const contentWidth = width - 4;
91
+ return clampLines(
92
+ [
93
+ `${renderBorder("├")}${renderBorder("─".repeat(width - 2))}${renderBorder("┤")}`,
94
+ ...lines.map(
95
+ (line) => `${renderBorder("│")} ${fillLine(line, contentWidth)} ${renderBorder("│")}`,
96
+ ),
97
+ ],
98
+ width,
99
+ );
100
+ }
101
+
102
+ function joinStyled(parts: string[], separator: string): string {
103
+ return parts.filter(Boolean).join(separator);
104
+ }
105
+
106
+ function thinkingStyle(config: ZentuiConfig, level: string): string | undefined {
107
+ switch (level.toLowerCase()) {
108
+ case "minimal":
109
+ return config.colors.editorThinkingMinimal ?? config.colors.editorThinking;
110
+ case "low":
111
+ return config.colors.editorThinkingLow ?? config.colors.editorThinking;
112
+ case "medium":
113
+ return config.colors.editorThinkingMedium ?? config.colors.editorThinking;
114
+ case "high":
115
+ return config.colors.editorThinkingHigh ?? config.colors.editorThinking;
116
+ case "xhigh":
117
+ return config.colors.editorThinkingXhigh ?? config.colors.editorThinking;
118
+ case "max":
119
+ return (
120
+ config.colors.editorThinkingMax ??
121
+ config.colors.editorThinkingXhigh ??
122
+ config.colors.editorThinking
123
+ );
124
+ default:
125
+ return config.colors.editorThinking;
126
+ }
127
+ }
128
+
129
+ function renderTopLeft(
130
+ inputText: string,
131
+ metadata: MinimalistEditorMetadata,
132
+ uiTheme: Theme,
133
+ config: ZentuiConfig,
134
+ includeSessionName = true,
135
+ ): string {
136
+ const source = config.components.editor.colorSource;
137
+ const trimmed = inputText.trimStart();
138
+ const bashMode = trimmed.startsWith("!!") ? "no-context" : trimmed.startsWith("!") ? "shell" : "";
139
+ const parts: string[] = [];
140
+ if (bashMode) {
141
+ parts.push(
142
+ bashMode === "no-context"
143
+ ? safeThemeFg(uiTheme, "muted", "$")
144
+ : safeThemeFg(uiTheme, "bashMode", "$"),
145
+ );
146
+ }
147
+ if (
148
+ config.components.editor.styles.minimalist.showTimer &&
149
+ metadata.agentDurationMs !== undefined
150
+ ) {
151
+ const duration = formatElapsedDuration(metadata.agentDurationMs);
152
+ parts.push(
153
+ metadata.agentActive
154
+ ? renderStyleForSourceOrFallback(
155
+ uiTheme,
156
+ source,
157
+ config.colors.sessionDuration,
158
+ EDITOR_ACCENT_FALLBACK,
159
+ duration,
160
+ )
161
+ : safeThemeFg(uiTheme, "muted", duration),
162
+ );
163
+ }
164
+ const sessionName = includeSessionName
165
+ ? sanitizeEditorMetadataText(metadata.sessionName ?? "")
166
+ : "";
167
+ if (config.components.editor.styles.minimalist.showSessionName && sessionName) {
168
+ parts.push(renderStyleForSource(uiTheme, source, config.colors.sessionName, sessionName));
169
+ }
170
+ return joinStyled(parts, safeThemeFg(uiTheme, "muted", " · "));
171
+ }
172
+
173
+ function renderTopRight(
174
+ metadata: MinimalistEditorMetadata,
175
+ uiTheme: Theme,
176
+ config: ZentuiConfig,
177
+ availableWidth: number,
178
+ renderBorder: (text: string) => string,
179
+ renderThinking: (text: string) => string,
180
+ ): string {
181
+ const source = config.components.editor.colorSource;
182
+ const parts: string[] = [];
183
+ const joinParts = (values: string[]) =>
184
+ values.map((part, index) => (index > 0 ? `${renderBorder(" – ")}${part}` : part)).join("");
185
+ const cost = config.components.editor.styles.minimalist.showCost
186
+ ? sanitizeEditorMetadataText(metadata.costLabel ?? "")
187
+ : "";
188
+ if (cost) {
189
+ parts.push(renderStyleForSource(uiTheme, source, config.colors.cost, cost));
190
+ }
191
+ const model = sanitizeEditorMetadataText(metadata.modelLabel ?? "");
192
+ if (model) {
193
+ parts.push(
194
+ renderStyleForSourceOrFallback(
195
+ uiTheme,
196
+ source,
197
+ config.colors.editorModel,
198
+ MINIMALIST_MODEL_FALLBACK,
199
+ model,
200
+ ),
201
+ );
202
+ }
203
+ const thinking = sanitizeEditorMetadataText(metadata.thinkingLevel ?? "");
204
+ if (thinking && thinking.toLowerCase() !== "off") {
205
+ parts.push(renderThinking(thinking));
206
+ }
207
+ if (metadata.contextPercent !== undefined && Number.isFinite(metadata.contextPercent)) {
208
+ const percent = Math.round(Math.max(0, Math.min(999, metadata.contextPercent)));
209
+ const tier = contextColorTier(
210
+ percent,
211
+ config.components.editor.styles.minimalist.contextThresholds,
212
+ );
213
+ const style =
214
+ tier === "error"
215
+ ? config.colors.contextError
216
+ : tier === "warning"
217
+ ? config.colors.contextWarning
218
+ : config.colors.contextNormal;
219
+ const total =
220
+ config.components.editor.styles.minimalist.contextFormat === "percent-total" &&
221
+ metadata.contextWindow !== undefined &&
222
+ Number.isFinite(metadata.contextWindow) &&
223
+ metadata.contextWindow > 0
224
+ ? `/${formatCount(metadata.contextWindow)}`
225
+ : "";
226
+ const text = `${percent}%${total}`;
227
+ let context = renderStyleForSource(uiTheme, source, style, text);
228
+ if (config.components.editor.styles.minimalist.contextGauge) {
229
+ for (const gaugeWidth of [5, 3]) {
230
+ const gauge = `[${buildContextGauge(percent, gaugeWidth, config.icons.mode === "ascii")}] ${text}`;
231
+ const styledGauge = renderStyleForSource(uiTheme, source, style, gauge);
232
+ const candidate = joinParts([...parts, styledGauge]);
233
+ if (visibleWidth(candidate) <= availableWidth) {
234
+ context = styledGauge;
235
+ break;
236
+ }
237
+ }
238
+ }
239
+ parts.push(context);
240
+ }
241
+ return joinParts(parts);
242
+ }
243
+
244
+ function renderBottomLeft(
245
+ metadata: MinimalistEditorMetadata,
246
+ uiTheme: Theme,
247
+ config: ZentuiConfig,
248
+ ): string {
249
+ if (!config.components.editor.styles.minimalist.showGit) return "";
250
+ const source = config.components.editor.colorSource;
251
+ const branch = sanitizeEditorMetadataText(metadata.branch ?? "");
252
+ const parts = branch
253
+ ? [
254
+ renderStyleForSourceOrFallback(
255
+ uiTheme,
256
+ source,
257
+ config.colors.editorGitBranch,
258
+ MINIMALIST_BRANCH_FALLBACK,
259
+ branch,
260
+ ),
261
+ ]
262
+ : [];
263
+ if (metadata.dirty) {
264
+ parts.push(renderStyleForSource(uiTheme, source, config.colors.gitStatus, "*"));
265
+ }
266
+ if ((metadata.ahead ?? 0) > 0) {
267
+ parts.push(safeThemeFg(uiTheme, "success", `↑${metadata.ahead}`));
268
+ }
269
+ if ((metadata.behind ?? 0) > 0) {
270
+ parts.push(safeThemeFg(uiTheme, "error", `↓${metadata.behind}`));
271
+ }
272
+ return parts.join(" ");
273
+ }
274
+
275
+ function minimalistCwdLabel(metadata: MinimalistEditorMetadata, config: ZentuiConfig): string {
276
+ const full = () => formatCwdLabel(metadata.cwd, "", { mode: "full", depth: 0 });
277
+ if (config.components.editor.styles.minimalist.pathDisplay === "full") return full();
278
+ if (config.components.editor.styles.minimalist.pathDisplay === "compact")
279
+ return basename(metadata.cwd) || metadata.cwd;
280
+ if (!metadata.projectRoot) return full();
281
+
282
+ const pathFromRoot = relative(metadata.projectRoot, metadata.cwd);
283
+ if (pathFromRoot === ".." || pathFromRoot.startsWith(`..${sep}`) || isAbsolute(pathFromRoot)) {
284
+ return full();
285
+ }
286
+ const project = basename(metadata.projectRoot) || metadata.projectRoot;
287
+ return pathFromRoot ? `${project}/${pathFromRoot}` : project;
288
+ }
289
+
290
+ function renderBottomRight(
291
+ metadata: MinimalistEditorMetadata,
292
+ uiTheme: Theme,
293
+ config: ZentuiConfig,
294
+ ): string {
295
+ const cwd = sanitizeEditorMetadataText(minimalistCwdLabel(metadata, config));
296
+ return cwd
297
+ ? renderStyleForSource(uiTheme, config.components.editor.colorSource, config.colors.cwd, cwd)
298
+ : "";
299
+ }
300
+
301
+ function renderLabeledBorder(options: {
302
+ width: number;
303
+ left: string;
304
+ leftFallbacks?: string[];
305
+ right: string;
306
+ leftCorner: string;
307
+ rightCorner: string;
308
+ renderBorder: (text: string) => string;
309
+ }): string {
310
+ const innerWidth = Math.max(0, options.width - 2);
311
+ let left = options.left;
312
+ let right = options.right;
313
+ const fitLabels = () => {
314
+ const overhead = (left ? 3 : 1) + (right ? 3 : 1);
315
+ const budget = Math.max(0, innerWidth - overhead);
316
+ const leftNatural = visibleWidth(left);
317
+ const rightNatural = visibleWidth(right);
318
+ if (leftNatural + rightNatural <= budget) return false;
319
+
320
+ let leftBudget = left ? budget : 0;
321
+ let rightBudget = right ? budget : 0;
322
+ if (left && right) {
323
+ leftBudget = Math.ceil(budget / 2);
324
+ rightBudget = budget - leftBudget;
325
+ if (leftNatural < leftBudget) {
326
+ leftBudget = leftNatural;
327
+ rightBudget = budget - leftBudget;
328
+ } else if (rightNatural < rightBudget) {
329
+ rightBudget = rightNatural;
330
+ leftBudget = budget - rightBudget;
331
+ }
332
+ }
333
+ left = leftBudget > 0 ? truncateToWidth(left, leftBudget, "…") : "";
334
+ right = rightBudget > 0 ? truncateToWidth(right, rightBudget, "…") : "";
335
+ return leftBudget < leftNatural;
336
+ };
337
+ let leftTruncated = fitLabels();
338
+ for (const fallback of options.leftFallbacks ?? []) {
339
+ if (!leftTruncated) break;
340
+ left = fallback;
341
+ right = options.right;
342
+ leftTruncated = fitLabels();
343
+ }
344
+ const partWidth = (label: string) => (label ? visibleWidth(label) + 3 : 1);
345
+ let leftWidth = partWidth(left);
346
+ let rightWidth = partWidth(right);
347
+ if (leftWidth + rightWidth > innerWidth) {
348
+ left = "";
349
+ right = "";
350
+ leftWidth = 1;
351
+ rightWidth = 1;
352
+ }
353
+
354
+ const fillWidth = Math.max(0, innerWidth - leftWidth - rightWidth);
355
+ const leftPart = left
356
+ ? `${options.renderBorder("─ ")}${left}${options.renderBorder(" ")}`
357
+ : options.renderBorder("─");
358
+ const rightPart = right
359
+ ? `${options.renderBorder(" ")}${right}${options.renderBorder(" ─")}`
360
+ : options.renderBorder("─");
361
+ return `${options.renderBorder(options.leftCorner)}${leftPart}${options.renderBorder(
362
+ "─".repeat(fillWidth),
363
+ )}${rightPart}${options.renderBorder(options.rightCorner)}`;
364
+ }
365
+
366
+ export function renderMinimalistFrame({
367
+ width,
368
+ editorLines,
369
+ autocompleteLines = [],
370
+ viewport,
371
+ inputText,
372
+ metadata,
373
+ uiTheme,
374
+ config,
375
+ borderColor,
376
+ }: MinimalistFrameOptions): string[] {
377
+ if (width <= 4) return clampLines(editorLines, width);
378
+ const contentWidth = Math.max(0, width - 4);
379
+ const source = config.components.editor.colorSource;
380
+ const adaptive = config.components.editor.borderColorMode === "adaptive";
381
+ const thinking = sanitizeEditorMetadataText(metadata.thinkingLevel ?? "");
382
+ const activeThinking = thinking && thinking.toLowerCase() !== "off" ? thinking : "";
383
+ const renderStaticBorder = (text: string) =>
384
+ renderStyleForSourceOrFallback(
385
+ uiTheme,
386
+ source,
387
+ config.colors.editorBorder,
388
+ EDITOR_BORDER_FALLBACK,
389
+ text,
390
+ );
391
+ const terminalAdaptiveThinkingStyle = activeThinking
392
+ ? (thinkingStyle(config, activeThinking) ??
393
+ MINIMALIST_ADAPTIVE_TERMINAL_THINKING_FALLBACKS[activeThinking.toLowerCase()] ??
394
+ MINIMALIST_THINKING_FALLBACK.terminal)
395
+ : undefined;
396
+ const renderBorder = (text: string) => {
397
+ if (!adaptive) return renderStaticBorder(text);
398
+ if (source === "terminal") {
399
+ return terminalAdaptiveThinkingStyle
400
+ ? renderStyleForSource(uiTheme, source, terminalAdaptiveThinkingStyle, text)
401
+ : renderStaticBorder(text);
402
+ }
403
+ if (!borderColor) return renderStaticBorder(text);
404
+ try {
405
+ const rendered = borderColor(text);
406
+ return typeof rendered === "string" ? rendered : renderStaticBorder(text);
407
+ } catch {
408
+ return renderStaticBorder(text);
409
+ }
410
+ };
411
+ const renderStaticThinking = (text: string) =>
412
+ renderStyleForSourceOrFallback(
413
+ uiTheme,
414
+ source,
415
+ thinkingStyle(config, text),
416
+ MINIMALIST_THINKING_FALLBACK,
417
+ text,
418
+ );
419
+ const renderThinking = adaptive ? renderBorder : renderStaticThinking;
420
+ const separator = safeThemeFg(uiTheme, "muted", " · ");
421
+ const viewportLabel = (direction: "above" | "below", count: string | undefined) => {
422
+ if (!count || !/^[1-9]\d*$/.test(count)) return "";
423
+ return safeThemeFg(uiTheme, "muted", `${direction === "above" ? "↑" : "↓"} ${count} more`);
424
+ };
425
+ const topMetadata = renderTopLeft(inputText, metadata, uiTheme, config);
426
+ const topOperational = renderTopLeft(inputText, metadata, uiTheme, config, false);
427
+ const topViewport = viewportLabel("above", viewport?.above);
428
+ const topLeft = joinStyled([topViewport, topMetadata], separator);
429
+ const topRightBudget = Math.max(0, width - 8 - visibleWidth(topLeft));
430
+ const topFallbacks = [
431
+ joinStyled([topViewport, topOperational], separator),
432
+ topOperational,
433
+ ].filter((value, index, values) => value !== topLeft && values.indexOf(value) === index);
434
+ const top = renderLabeledBorder({
435
+ width,
436
+ left: topLeft,
437
+ leftFallbacks: topFallbacks,
438
+ right: renderTopRight(metadata, uiTheme, config, topRightBudget, renderBorder, renderThinking),
439
+ leftCorner: "╭",
440
+ rightCorner: "╮",
441
+ renderBorder,
442
+ });
443
+ const bottomMetadata = renderBottomLeft(metadata, uiTheme, config);
444
+ const bottomViewport = viewportLabel("below", viewport?.below);
445
+ const bottom = renderLabeledBorder({
446
+ width,
447
+ left: joinStyled([bottomViewport, bottomMetadata], separator),
448
+ leftFallbacks: bottomViewport ? [bottomMetadata] : undefined,
449
+ right: renderBottomRight(metadata, uiTheme, config),
450
+ leftCorner: "╰",
451
+ rightCorner: "╯",
452
+ renderBorder,
453
+ });
454
+ const content = editorLines.map(
455
+ (line) => `${renderBorder("│")} ${fillLine(line, contentWidth)} ${renderBorder("│")}`,
456
+ );
457
+ const autocomplete = renderFramedAutocompleteRows({
458
+ width,
459
+ lines: autocompleteLines,
460
+ renderBorder,
461
+ });
462
+ return clampLines([top, ...content, ...autocomplete, bottom], width);
463
+ }