@selesai/code 0.9.15 → 0.10.1

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 (269) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +2 -2
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +2 -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/modes/interactive/components/settings-selector.d.ts +1 -1
  118. package/dist/modes/interactive/interactive-mode.js +1 -1
  119. package/dist/skills/design-references/SKILL.md +31 -0
  120. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  121. package/dist/themes/adventure.json +84 -0
  122. package/dist/themes/adwaita-dark.json +84 -0
  123. package/dist/themes/arcoiris.json +84 -0
  124. package/dist/themes/arthur.json +84 -0
  125. package/dist/themes/atom.json +84 -0
  126. package/dist/themes/aura.json +84 -0
  127. package/dist/themes/black-metal-bathory.json +84 -0
  128. package/dist/themes/black-metal-burzum.json +84 -0
  129. package/dist/themes/black-metal-khold.json +84 -0
  130. package/dist/themes/box.json +84 -0
  131. package/dist/themes/brogrammer.json +84 -0
  132. package/dist/themes/carbonfox.json +84 -0
  133. package/dist/themes/catppuccin-mocha.json +84 -0
  134. package/dist/themes/citruszest.json +84 -0
  135. package/dist/themes/cursor-dark.json +84 -0
  136. package/dist/themes/cutie-pro.json +84 -0
  137. package/dist/themes/dark-modern.json +84 -0
  138. package/dist/themes/dark-pastel.json +84 -0
  139. package/dist/themes/dimmed-monokai.json +84 -0
  140. package/dist/themes/doom-peacock.json +84 -0
  141. package/dist/themes/dracula-plus.json +84 -0
  142. package/dist/themes/dracula.json +80 -0
  143. package/dist/themes/earthsong.json +84 -0
  144. package/dist/themes/everforest-dark-hard.json +84 -0
  145. package/dist/themes/everforest-dark.json +80 -0
  146. package/dist/themes/fahrenheit.json +84 -0
  147. package/dist/themes/flatland.json +84 -0
  148. package/dist/themes/flexoki-dark.json +84 -0
  149. package/dist/themes/front-end-delight.json +84 -0
  150. package/dist/themes/fun-forrest.json +84 -0
  151. package/dist/themes/galizur.json +84 -0
  152. package/dist/themes/github-dark-colorblind.json +84 -0
  153. package/dist/themes/github-dark-high-contrast.json +84 -0
  154. package/dist/themes/glacier.json +84 -0
  155. package/dist/themes/gruber-darker.json +84 -0
  156. package/dist/themes/gruvbox-dark-hard.json +84 -0
  157. package/dist/themes/gruvbox-dark.json +84 -0
  158. package/dist/themes/gruvbox-material.json +84 -0
  159. package/dist/themes/guezwhoz.json +84 -0
  160. package/dist/themes/hacktober.json +84 -0
  161. package/dist/themes/hardcore.json +84 -0
  162. package/dist/themes/havn-skumring.json +84 -0
  163. package/dist/themes/ic-orange-ppl.json +84 -0
  164. package/dist/themes/iterm2-smoooooth.json +84 -0
  165. package/dist/themes/iterm2-tango-dark.json +84 -0
  166. package/dist/themes/japanesque.json +84 -0
  167. package/dist/themes/jellybeans.json +84 -0
  168. package/dist/themes/kanagawa-wave.json +84 -0
  169. package/dist/themes/kurokula.json +84 -0
  170. package/dist/themes/later-this-evening.json +84 -0
  171. package/dist/themes/lovelace.json +84 -0
  172. package/dist/themes/material-darker.json +84 -0
  173. package/dist/themes/matte-black.json +84 -0
  174. package/dist/themes/mellow.json +84 -0
  175. package/dist/themes/miasma.json +84 -0
  176. package/dist/themes/nord.json +80 -0
  177. package/dist/themes/nvim-dark.json +84 -0
  178. package/dist/themes/one-dark.json +80 -0
  179. package/dist/themes/popping-and-locking.json +84 -0
  180. package/dist/themes/rose-pine.json +80 -0
  181. package/dist/themes/sea-shells.json +84 -0
  182. package/dist/themes/sleepy-hollow.json +84 -0
  183. package/dist/themes/smyck.json +84 -0
  184. package/dist/themes/solarized-dark.json +80 -0
  185. package/dist/themes/tokyo-night.json +80 -0
  186. package/dist/themes/tomorrow-night-bright.json +84 -0
  187. package/dist/themes/tomorrow-night-burns.json +84 -0
  188. package/dist/themes/tomorrow-night.json +84 -0
  189. package/dist/themes/twilight.json +84 -0
  190. package/dist/themes/vague.json +84 -0
  191. package/dist/themes/vesper.json +84 -0
  192. package/dist/themes/xcode-dark-hc.json +84 -0
  193. package/docs/custom-provider.md +0 -1
  194. package/package.json +3 -3
  195. package/dist/extensions/auto-model/auto-model.test.ts +0 -18
  196. package/dist/extensions/auto-model/classifier.ts +0 -23
  197. package/dist/extensions/auto-model/config.ts +0 -51
  198. package/dist/extensions/auto-model/index.ts +0 -22
  199. package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
  200. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  201. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  202. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  203. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  204. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  205. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  206. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  207. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  208. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  209. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  210. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  211. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  212. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  213. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  214. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  215. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  216. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  217. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  218. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  219. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  220. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  221. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  222. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  223. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  224. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  225. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  226. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  227. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  228. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  229. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  230. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  231. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  232. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  233. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  234. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  235. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  236. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  237. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  238. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  239. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  240. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  241. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  242. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  243. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  244. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  245. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  246. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  247. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  248. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  249. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  250. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  251. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  252. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  253. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  254. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  255. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  256. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  257. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  258. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  259. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  260. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  261. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  262. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  263. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  264. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  265. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  266. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  267. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  268. package/dist/extensions/preview-tools-disabled.ts +0 -11
  269. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -0,0 +1,128 @@
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
2
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
+ import { renderAccentRailEditorFrame } from "./accent-rail-editor";
4
+ import type { PolishedTuiConfig } from "./config";
5
+ import { sanitizeEditorMetadataText } from "./editor-metadata-format";
6
+ import { renderMinimalistFrame } from "./minimalist-editor";
7
+ import { safeThemeFg } from "./style";
8
+ import { renderPolishedEditorFrame } from "./ui";
9
+ import { renderUserMessageStyle } from "./user-message-styles";
10
+
11
+ export const SETTINGS_PREVIEW_MAX_WIDTH = 72;
12
+ export const SETTINGS_PREVIEW_MAX_ROWS = 10;
13
+ export const EDITOR_PREVIEW_INPUT = "Explain this change safely.";
14
+ export const USER_MESSAGE_PREVIEW_MARKDOWN = "Please review **this change** safely.";
15
+
16
+ function boundedRows(rows: string[], width: number): string[] {
17
+ const safeWidth = Math.max(0, Math.min(SETTINGS_PREVIEW_MAX_WIDTH, width));
18
+ if (safeWidth <= 0) return [];
19
+ const bounded = rows
20
+ .slice(0, SETTINGS_PREVIEW_MAX_ROWS)
21
+ .map((line) => truncateToWidth(line, safeWidth, ""));
22
+ while (bounded.length > 0 && visibleWidth(bounded.at(-1) ?? "") === 0) bounded.pop();
23
+ return bounded;
24
+ }
25
+
26
+ function previewConfig(config: PolishedTuiConfig): PolishedTuiConfig {
27
+ const derived = structuredClone(config);
28
+ derived.icons = Object.fromEntries(
29
+ Object.entries(derived.icons).map(([key, value]) => [
30
+ key,
31
+ typeof value === "string" ? sanitizeEditorMetadataText(value) : value,
32
+ ]),
33
+ ) as typeof derived.icons;
34
+ return derived;
35
+ }
36
+
37
+ function adaptiveBorder(theme: Theme): (text: string) => string {
38
+ return (text) => safeThemeFg(theme, "thinkingHigh", text);
39
+ }
40
+
41
+ export function renderEditorSettingsPreview(
42
+ config: PolishedTuiConfig,
43
+ theme: Theme,
44
+ width: number,
45
+ ): string[] {
46
+ const previewWidth = Math.max(0, Math.min(SETTINGS_PREVIEW_MAX_WIDTH, width));
47
+ if (previewWidth <= 0) return [];
48
+ const safeConfig = previewConfig(config);
49
+ const editor = safeConfig.components.editor;
50
+ const borderColor = adaptiveBorder(theme);
51
+ const modelLabel = editor.modelLabel === "name" ? "Sonnet 4" : "sonnet-4";
52
+ const editorLines = [EDITOR_PREVIEW_INPUT];
53
+ const autocompleteLines = [
54
+ safeThemeFg(theme, "accent", "→ settings Open settings"),
55
+ " files Search files",
56
+ safeThemeFg(theme, "muted", " (1/47)"),
57
+ ];
58
+ const viewport = editor.viewportIndicators ? { above: "2", below: "3" } : undefined;
59
+ let frame: string[];
60
+ if (editor.style === "accent-rail") {
61
+ frame = renderAccentRailEditorFrame({
62
+ width: previewWidth,
63
+ editorLines,
64
+ autocompleteLines,
65
+ viewport,
66
+ uiTheme: theme,
67
+ config: safeConfig,
68
+ });
69
+ } else if (editor.style === "minimalist") {
70
+ frame = renderMinimalistFrame({
71
+ width: previewWidth,
72
+ editorLines,
73
+ viewport,
74
+ inputText: EDITOR_PREVIEW_INPUT,
75
+ metadata: {
76
+ cwd: "/workspace/zentui/src",
77
+ projectRoot: "/workspace/zentui",
78
+ branch: "feat/settings-previews",
79
+ dirty: true,
80
+ ahead: 2,
81
+ behind: 1,
82
+ costLabel: "$.12",
83
+ modelLabel,
84
+ thinkingLevel: "high",
85
+ contextPercent: 75,
86
+ contextWindow: 372_000,
87
+ sessionName: "Preview",
88
+ agentDurationMs: 12_000,
89
+ agentActive: true,
90
+ },
91
+ uiTheme: theme,
92
+ config: safeConfig,
93
+ borderColor,
94
+ });
95
+ } else {
96
+ frame = renderPolishedEditorFrame({
97
+ width: previewWidth,
98
+ editorLines,
99
+ autocompleteLines,
100
+ viewport,
101
+ uiTheme: theme,
102
+ config: safeConfig,
103
+ modelMeta: {
104
+ modelLabel,
105
+ modelId: "sonnet-4",
106
+ modelName: "Sonnet 4",
107
+ providerLabel: "Anthropic",
108
+ sessionName: "Preview",
109
+ },
110
+ thinkingLevel: "high",
111
+ borderColor,
112
+ });
113
+ }
114
+ return boundedRows(frame, previewWidth);
115
+ }
116
+
117
+ export function renderUserMessageSettingsPreview(
118
+ config: PolishedTuiConfig,
119
+ theme: Theme,
120
+ width: number,
121
+ text = USER_MESSAGE_PREVIEW_MARKDOWN,
122
+ ): string[] {
123
+ const previewWidth = Math.max(0, Math.min(SETTINGS_PREVIEW_MAX_WIDTH, width));
124
+ if (previewWidth <= 0) return [];
125
+ const safeConfig = previewConfig(config);
126
+ const frame = renderUserMessageStyle({ text, width: previewWidth, theme, config: safeConfig });
127
+ return boundedRows(frame, previewWidth);
128
+ }
@@ -0,0 +1,94 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { ModelLabelSource } from "./config";
3
+ import {
4
+ buildCacheReadLabel,
5
+ buildCacheWriteLabel,
6
+ buildContextLabel,
7
+ buildCostLabel,
8
+ buildTokenLabel,
9
+ formatProviderLabel,
10
+ getUsageTotals,
11
+ type UsageTotals,
12
+ } from "./format";
13
+ import type { GitStatusSummary } from "./git";
14
+ import type { PackageVersionResult } from "./package-version";
15
+ import type { RuntimeInfo } from "./runtime";
16
+ import type { FooterTelemetry } from "./telemetry";
17
+
18
+ export type FooterState = GitStatusSummary & {
19
+ modelLabel: string;
20
+ modelId: string;
21
+ modelName: string;
22
+ providerLabel: string;
23
+ contextLabel: string;
24
+ tokenLabel: string;
25
+ cacheReadLabel: string;
26
+ cacheWriteLabel: string;
27
+ costLabel: string;
28
+ usageTotals: UsageTotals;
29
+ subscription: boolean;
30
+ autoCompaction: boolean;
31
+ runtime?: RuntimeInfo;
32
+ packageVersion?: PackageVersionResult;
33
+ sessionStartEpoch?: number;
34
+ };
35
+
36
+ export function createInitialState(gitDefaults: GitStatusSummary): FooterState {
37
+ return {
38
+ modelLabel: "no-model",
39
+ modelId: "",
40
+ modelName: "",
41
+ providerLabel: "Unknown",
42
+ contextLabel: "--",
43
+ tokenLabel: "↑0 ↓0",
44
+ cacheReadLabel: "",
45
+ cacheWriteLabel: "",
46
+ costLabel: "$0.000",
47
+ usageTotals: {
48
+ input: 0,
49
+ output: 0,
50
+ cacheRead: 0,
51
+ cacheWrite: 0,
52
+ latestCacheHitRate: undefined,
53
+ cost: 0,
54
+ },
55
+ subscription: false,
56
+ autoCompaction: false,
57
+ runtime: undefined,
58
+ packageVersion: undefined,
59
+ sessionStartEpoch: Date.now(),
60
+ ...gitDefaults,
61
+ };
62
+ }
63
+
64
+ export function modelLabelFor(
65
+ state: Pick<FooterState, "modelId" | "modelName">,
66
+ source: ModelLabelSource,
67
+ ): string {
68
+ return source === "name"
69
+ ? state.modelName || state.modelId || "no-model"
70
+ : state.modelId || "no-model";
71
+ }
72
+
73
+ export function syncState(
74
+ state: FooterState,
75
+ ctx: ExtensionContext,
76
+ cacheHitIcon: string,
77
+ telemetry: FooterTelemetry = {},
78
+ ): void {
79
+ const totals = getUsageTotals(ctx);
80
+ const m = ctx.model;
81
+ state.modelId = m?.id ?? "";
82
+ state.modelName = m?.name ?? "";
83
+ // Retained as a compatibility snapshot only; production surfaces format from raw fields.
84
+ state.modelLabel = modelLabelFor(state, "id");
85
+ state.providerLabel = formatProviderLabel(ctx.model?.provider);
86
+ state.contextLabel = buildContextLabel(ctx);
87
+ state.tokenLabel = buildTokenLabel(totals, cacheHitIcon);
88
+ state.cacheReadLabel = buildCacheReadLabel(totals.cacheRead);
89
+ state.cacheWriteLabel = buildCacheWriteLabel(totals.cacheWrite);
90
+ state.costLabel = buildCostLabel(totals);
91
+ state.usageTotals = totals;
92
+ state.subscription = telemetry.subscription === true;
93
+ state.autoCompaction = telemetry.autoCompaction === true;
94
+ }
@@ -0,0 +1,424 @@
1
+ import type { ThemeColor } from "@earendil-works/pi-coding-agent";
2
+ import type { ColorSource, ColorSpec } from "./config";
3
+
4
+ type ThemeLike = {
5
+ fg(color: string, text: string): string;
6
+ bold?: (text: string) => string;
7
+ italic?: (text: string) => string;
8
+ underline?: (text: string) => string;
9
+ };
10
+
11
+ export type { ThemeLike };
12
+
13
+ export const EDITOR_ACCENT_STYLE = "blue";
14
+ export const EDITOR_BORDER_STYLE = "bright-black";
15
+ export const MAX_SAFE_SGR_PREFIX_CODE_UNITS = 96;
16
+ export const MAX_SAFE_SGR_PREFIX_SEQUENCES = 4;
17
+
18
+ export type SourceStyleFallback = {
19
+ theme: ColorSpec;
20
+ terminal: ColorSpec;
21
+ };
22
+
23
+ export const EDITOR_ACCENT_FALLBACK: SourceStyleFallback = {
24
+ theme: "accent",
25
+ terminal: EDITOR_ACCENT_STYLE,
26
+ };
27
+
28
+ export const EDITOR_BORDER_FALLBACK: SourceStyleFallback = {
29
+ theme: "borderMuted",
30
+ terminal: EDITOR_BORDER_STYLE,
31
+ };
32
+
33
+ function isHexColor(value: string): boolean {
34
+ return /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value);
35
+ }
36
+
37
+ function expandHexColor(hex: string): string {
38
+ const body = hex.slice(1);
39
+ if (body.length === 3) {
40
+ return body
41
+ .split("")
42
+ .map((ch) => ch + ch)
43
+ .join("");
44
+ }
45
+ return body;
46
+ }
47
+
48
+ function hexToAnsi(hex: string, isBackground = false): string {
49
+ const normalized = expandHexColor(hex);
50
+ const r = Number.parseInt(normalized.slice(0, 2), 16);
51
+ const g = Number.parseInt(normalized.slice(2, 4), 16);
52
+ const b = Number.parseInt(normalized.slice(4, 6), 16);
53
+ return `\x1b[${isBackground ? 48 : 38};2;${r};${g};${b}m`;
54
+ }
55
+
56
+ const terminalColorCodes = new Map([
57
+ ["black", 30],
58
+ ["red", 31],
59
+ ["green", 32],
60
+ ["yellow", 33],
61
+ ["blue", 34],
62
+ ["purple", 35],
63
+ ["cyan", 36],
64
+ ["white", 37],
65
+ ["bright-black", 90],
66
+ ["bright-red", 91],
67
+ ["bright-green", 92],
68
+ ["bright-yellow", 93],
69
+ ["bright-blue", 94],
70
+ ["bright-purple", 95],
71
+ ["bright-cyan", 96],
72
+ ["bright-white", 97],
73
+ ]);
74
+
75
+ const terminalStyleModifiers = new Map([
76
+ ["bold", 1],
77
+ ["dim", 2],
78
+ ["dimmed", 2],
79
+ ["italic", 3],
80
+ ["underline", 4],
81
+ ]);
82
+
83
+ const themeColorNameMap = new Map([
84
+ ["red", "error"],
85
+ ["bright-red", "error"],
86
+ ["green", "success"],
87
+ ["bright-green", "success"],
88
+ ["yellow", "warning"],
89
+ ["bright-yellow", "warning"],
90
+ ["blue", "syntaxFunction"],
91
+ ["bright-blue", "syntaxFunction"],
92
+ ["cyan", "syntaxFunction"],
93
+ ["bright-cyan", "syntaxFunction"],
94
+ ["purple", "syntaxKeyword"],
95
+ ["bright-purple", "syntaxKeyword"],
96
+ ["black", "muted"],
97
+ ["bright-black", "muted"],
98
+ ["white", "text"],
99
+ ["bright-white", "text"],
100
+ ]);
101
+
102
+ const themeStyleModifiers = new Set(["bold", "italic", "underline"]);
103
+
104
+ const themeColorTokens = new Set<ThemeColor>([
105
+ "accent",
106
+ "border",
107
+ "borderAccent",
108
+ "borderMuted",
109
+ "success",
110
+ "error",
111
+ "warning",
112
+ "muted",
113
+ "dim",
114
+ "text",
115
+ "thinkingText",
116
+ "userMessageText",
117
+ "customMessageText",
118
+ "customMessageLabel",
119
+ "toolTitle",
120
+ "toolOutput",
121
+ "mdHeading",
122
+ "mdLink",
123
+ "mdLinkUrl",
124
+ "mdCode",
125
+ "mdCodeBlock",
126
+ "mdCodeBlockBorder",
127
+ "mdQuote",
128
+ "mdQuoteBorder",
129
+ "mdHr",
130
+ "mdListBullet",
131
+ "toolDiffAdded",
132
+ "toolDiffRemoved",
133
+ "toolDiffContext",
134
+ "syntaxComment",
135
+ "syntaxKeyword",
136
+ "syntaxFunction",
137
+ "syntaxVariable",
138
+ "syntaxString",
139
+ "syntaxNumber",
140
+ "syntaxType",
141
+ "syntaxOperator",
142
+ "syntaxPunctuation",
143
+ "thinkingOff",
144
+ "thinkingMinimal",
145
+ "thinkingLow",
146
+ "thinkingMedium",
147
+ "thinkingHigh",
148
+ "thinkingXhigh",
149
+ "thinkingMax",
150
+ "bashMode",
151
+ ]);
152
+
153
+ function terminalColorToAnsi(color: string, isBackground = false): string | undefined {
154
+ const normalized = color.toLowerCase();
155
+ const colorCode = terminalColorCodes.get(normalized);
156
+ if (colorCode !== undefined) return `${isBackground ? colorCode + 10 : colorCode}`;
157
+
158
+ if (/^(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(normalized)) {
159
+ return `${isBackground ? 48 : 38};5;${normalized}`;
160
+ }
161
+
162
+ if (isHexColor(normalized)) return hexToAnsi(normalized, isBackground).slice(2, -1);
163
+ return undefined;
164
+ }
165
+
166
+ function isExplicitTerminalColorToken(token: string): boolean {
167
+ const normalized = token.toLowerCase();
168
+ if (normalized.startsWith("fg:") || normalized.startsWith("bg:")) return true;
169
+ if (isHexColor(normalized)) return true;
170
+ return /^(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(normalized);
171
+ }
172
+
173
+ function isSupportedStyleToken(token: string): boolean {
174
+ const normalized = token.toLowerCase();
175
+ if (terminalStyleModifiers.has(normalized)) return true;
176
+ if (terminalColorToAnsi(normalized) !== undefined) return true;
177
+
178
+ const isForeground = normalized.startsWith("fg:");
179
+ const isBackground = normalized.startsWith("bg:");
180
+ if (isForeground || isBackground) {
181
+ return terminalColorToAnsi(normalized.slice(3), isBackground) !== undefined;
182
+ }
183
+
184
+ return themeColorTokens.has(token as ThemeColor);
185
+ }
186
+
187
+ export function isSupportedColorSpec(style: ColorSpec): boolean {
188
+ const trimmed = style.trim();
189
+ if (trimmed === "") return true;
190
+ return trimmed.split(/\s+/).every(isSupportedStyleToken);
191
+ }
192
+
193
+ function applyThemeModifiers(theme: ThemeLike, styleTokens: string[], text: string): string {
194
+ let rendered = text;
195
+ for (const token of styleTokens) {
196
+ const normalized = token.toLowerCase();
197
+ if (normalized === "bold") rendered = theme.bold?.(rendered) ?? rendered;
198
+ if (normalized === "italic") rendered = theme.italic?.(rendered) ?? rendered;
199
+ if (normalized === "underline") rendered = theme.underline?.(rendered) ?? rendered;
200
+ }
201
+ return rendered;
202
+ }
203
+
204
+ export function safeThemeFg(theme: ThemeLike, color: string, text: string): string {
205
+ try {
206
+ return theme.fg(color, text);
207
+ } catch {
208
+ return text;
209
+ }
210
+ }
211
+
212
+ function mapThemeColor(styleTokens: string[]): string | undefined {
213
+ let fallback: string | undefined;
214
+ for (const token of styleTokens) {
215
+ const normalized = token.toLowerCase();
216
+ if (themeStyleModifiers.has(normalized)) continue;
217
+ if (normalized === "dim" || normalized === "dimmed") {
218
+ fallback = "muted";
219
+ continue;
220
+ }
221
+
222
+ const mapped = themeColorNameMap.get(normalized);
223
+ if (mapped) return mapped;
224
+ return token;
225
+ }
226
+ return fallback;
227
+ }
228
+
229
+ /**
230
+ * Colorize text using a theme color token or hex color.
231
+ * Non-hex values are passed directly to `theme.fg()`; invalid tokens fall back
232
+ * to unstyled text so a config typo does not break rendering.
233
+ */
234
+ export function colorize(theme: ThemeLike, color: ColorSpec, text: string): string {
235
+ if (isHexColor(color)) {
236
+ return `${hexToAnsi(color)}${text}\x1b[39m`;
237
+ }
238
+ return safeThemeFg(theme, color, text);
239
+ }
240
+
241
+ /**
242
+ * Render text with Starship-style terminal styling strings (e.g. "bold red", "fg:202",
243
+ * "bg:blue", "underline bg:#bf5700").
244
+ */
245
+ function isSafeSgrParameters(parameters: string): boolean {
246
+ const tokens = parameters.split(";");
247
+ if (tokens.some((token) => !/^\d+$/.test(token))) return false;
248
+ const values = tokens.map(Number);
249
+ for (let index = 0; index < values.length; index += 1) {
250
+ const value = values[index];
251
+ if (
252
+ value === 1 ||
253
+ value === 2 ||
254
+ value === 3 ||
255
+ value === 4 ||
256
+ (value !== undefined && value >= 30 && value <= 37) ||
257
+ (value !== undefined && value >= 40 && value <= 47) ||
258
+ (value !== undefined && value >= 90 && value <= 97) ||
259
+ (value !== undefined && value >= 100 && value <= 107)
260
+ ) {
261
+ continue;
262
+ }
263
+ if (value !== 38 && value !== 48) return false;
264
+ const mode = values[index + 1];
265
+ if (mode === 5) {
266
+ const color = values[index + 2];
267
+ if (color === undefined || color < 0 || color > 255) return false;
268
+ index += 2;
269
+ continue;
270
+ }
271
+ if (mode === 2) {
272
+ const channels = values.slice(index + 2, index + 5);
273
+ if (channels.length !== 3 || channels.some((channel) => channel < 0 || channel > 255)) {
274
+ return false;
275
+ }
276
+ index += 4;
277
+ continue;
278
+ }
279
+ return false;
280
+ }
281
+ return true;
282
+ }
283
+
284
+ /** Accept only the bounded SGR subset emitted by Zentui's supported style tokens. */
285
+ export function isSafeSgrStylePrefix(value: unknown): value is string {
286
+ if (
287
+ typeof value !== "string" ||
288
+ value.length === 0 ||
289
+ value.length > MAX_SAFE_SGR_PREFIX_CODE_UNITS
290
+ ) {
291
+ return false;
292
+ }
293
+ const sequence = /\x1b\[([0-9]+(?:;[0-9]+)*)m/g;
294
+ let offset = 0;
295
+ let count = 0;
296
+ for (const match of value.matchAll(sequence)) {
297
+ if (match.index !== offset || !isSafeSgrParameters(match[1] ?? "")) return false;
298
+ offset = match.index + match[0].length;
299
+ count += 1;
300
+ if (count > MAX_SAFE_SGR_PREFIX_SEQUENCES) return false;
301
+ }
302
+ return count > 0 && offset === value.length;
303
+ }
304
+
305
+ export function renderTerminalStyle(style: string, text: string): string {
306
+ const codes: string[] = [];
307
+ for (const token of style.trim().split(/\s+/)) {
308
+ if (!token) continue;
309
+
310
+ const normalized = token.toLowerCase();
311
+ const modifier = terminalStyleModifiers.get(normalized);
312
+ if (modifier !== undefined) {
313
+ codes.push(`${modifier}`);
314
+ continue;
315
+ }
316
+
317
+ const isForeground = normalized.startsWith("fg:");
318
+ const isBackground = normalized.startsWith("bg:");
319
+ const colorName = isForeground || isBackground ? normalized.slice(3) : normalized;
320
+ const color = terminalColorToAnsi(colorName, isBackground);
321
+ if (color) codes.push(color);
322
+ }
323
+
324
+ return codes.length ? `\x1b[${codes.join(";")}m${text}\x1b[0m` : text;
325
+ }
326
+
327
+ /**
328
+ * Apply Starship-style terminal styling first, falling back to Pi theme tokens for
329
+ * legacy config values such as "accent" or "syntaxKeyword".
330
+ */
331
+ export function renderStyle(theme: ThemeLike, style: ColorSpec, text: string): string {
332
+ if (style.trim() === "") return text;
333
+ const styled = renderTerminalStyle(style, text);
334
+ return styled === text ? colorize(theme, style, text) : styled;
335
+ }
336
+
337
+ export function renderThemeStyle(theme: ThemeLike, style: ColorSpec, text: string): string {
338
+ const trimmed = style.trim();
339
+ if (trimmed === "") return text;
340
+
341
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
342
+ if (tokens.some(isExplicitTerminalColorToken)) return renderTerminalStyle(style, text);
343
+
344
+ const color = mapThemeColor(tokens) ?? "text";
345
+ return safeThemeFg(theme, color, applyThemeModifiers(theme, tokens, text));
346
+ }
347
+
348
+ function renderStyleStrict(theme: ThemeLike, style: ColorSpec, text: string): string {
349
+ if (style.trim() === "") return text;
350
+ const styled = renderTerminalStyle(style, text);
351
+ return styled === text ? theme.fg(style, text) : styled;
352
+ }
353
+
354
+ function renderThemeStyleStrict(theme: ThemeLike, style: ColorSpec, text: string): string {
355
+ const trimmed = style.trim();
356
+ if (trimmed === "") return text;
357
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
358
+ if (tokens.some(isExplicitTerminalColorToken)) return renderTerminalStyle(style, text);
359
+ const color = mapThemeColor(tokens) ?? "text";
360
+ return theme.fg(color, applyThemeModifiers(theme, tokens, text));
361
+ }
362
+
363
+ export function renderStyleForSource(
364
+ theme: ThemeLike,
365
+ source: ColorSource,
366
+ style: ColorSpec,
367
+ text: string,
368
+ ): string {
369
+ return source === "terminal"
370
+ ? renderStyle(theme, style, text)
371
+ : renderThemeStyle(theme, style, text);
372
+ }
373
+
374
+ export function renderStyleForSourceOrFallback(
375
+ theme: ThemeLike,
376
+ source: ColorSource,
377
+ style: ColorSpec | undefined,
378
+ fallback: ColorSpec | SourceStyleFallback,
379
+ text: string,
380
+ ): string {
381
+ const fallbackStyle = typeof fallback === "string" ? fallback : fallback[source];
382
+ return renderStyleForSource(theme, source, style ?? fallbackStyle, text);
383
+ }
384
+
385
+ export function renderStyleForSourceOrFallbackStrict(
386
+ theme: ThemeLike,
387
+ source: ColorSource,
388
+ style: ColorSpec | undefined,
389
+ fallback: ColorSpec | SourceStyleFallback,
390
+ text: string,
391
+ ): string {
392
+ const fallbackStyle = typeof fallback === "string" ? fallback : fallback[source];
393
+ const resolvedStyle = style ?? fallbackStyle;
394
+ return source === "terminal"
395
+ ? renderStyleStrict(theme, resolvedStyle, text)
396
+ : renderThemeStyleStrict(theme, resolvedStyle, text);
397
+ }
398
+
399
+ export function renderEditorAccent(text: string): string {
400
+ return renderTerminalStyle(EDITOR_ACCENT_STYLE, text);
401
+ }
402
+
403
+ export function renderEditorBorder(text: string): string {
404
+ return renderTerminalStyle(EDITOR_BORDER_STYLE, text);
405
+ }
406
+
407
+ export function renderAccentLine(theme: ThemeLike, source: ColorSource, text: string): string {
408
+ return renderStyleForSourceOrFallback(theme, source, undefined, EDITOR_ACCENT_FALLBACK, text);
409
+ }
410
+
411
+ export function renderChromeBorder(
412
+ theme: ThemeLike,
413
+ source: ColorSource,
414
+ terminalFallbackStyle: ColorSpec,
415
+ text: string,
416
+ ): string {
417
+ return renderStyleForSourceOrFallback(
418
+ theme,
419
+ source,
420
+ undefined,
421
+ { theme: "borderMuted", terminal: terminalFallbackStyle },
422
+ text,
423
+ );
424
+ }