@selesai/code 0.9.15 → 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 (267) hide show
  1. package/CHANGELOG.md +22 -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/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/package.json +3 -3
  193. package/dist/extensions/auto-model/auto-model.test.ts +0 -18
  194. package/dist/extensions/auto-model/classifier.ts +0 -23
  195. package/dist/extensions/auto-model/config.ts +0 -51
  196. package/dist/extensions/auto-model/index.ts +0 -22
  197. package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
  198. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  199. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  200. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  201. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  202. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  203. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  204. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  205. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  206. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  207. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  208. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  209. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  210. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  211. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  212. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  213. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  214. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  215. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  216. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  217. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  218. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  219. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  220. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  221. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  222. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  223. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  224. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  225. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  226. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  227. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  228. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  229. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  230. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  231. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  232. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  233. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  234. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  235. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  236. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  237. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  238. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  239. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  240. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  241. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  242. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  243. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  244. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  245. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  246. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  247. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  248. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  249. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  250. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  251. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  252. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  253. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  254. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  255. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  256. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  257. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  258. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  259. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  260. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  261. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  262. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  263. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  264. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  265. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  266. package/dist/extensions/preview-tools-disabled.ts +0 -11
  267. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -0,0 +1,457 @@
1
+ import { readFileSync, realpathSync, statSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join, parse } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ export const ZENTUI_ACCENT_RAIL_LAYOUT_EDITOR = Symbol.for("pi-zentui.accent-rail-layout-editor");
7
+ const ZENTUI_ACCENT_RAIL_LAYOUT_REGISTRY = Symbol.for("pi-zentui.accent-rail-layout-registry");
8
+ const LAYOUT_NODE = Symbol.for("@earendil-works/pi-tui/layout-node");
9
+
10
+ const SUPPORTED_PI_TUI_VERSION = /^0\.84\.\d+$/;
11
+
12
+ type AccentRailEditorMarker = {
13
+ owner: symbol;
14
+ active: () => boolean;
15
+ };
16
+
17
+ type MarkedEditor = {
18
+ [ZENTUI_ACCENT_RAIL_LAYOUT_EDITOR]?: AccentRailEditorMarker;
19
+ };
20
+
21
+ type StackEntry = {
22
+ component?: unknown;
23
+ minSize?: number;
24
+ shrink?: number;
25
+ [key: PropertyKey]: unknown;
26
+ };
27
+
28
+ type LayoutNode = {
29
+ type?: unknown;
30
+ entries?: unknown;
31
+ [key: PropertyKey]: unknown;
32
+ };
33
+
34
+ type LayoutMethod = (this: unknown, ...args: unknown[]) => unknown;
35
+
36
+ type LayoutPatchRegistry = {
37
+ method: symbol;
38
+ predecessor: LayoutMethod;
39
+ predecessorDescriptor?: PropertyDescriptor;
40
+ wrapper: LayoutMethod;
41
+ owners: Map<symbol, number>;
42
+ active: boolean;
43
+ };
44
+
45
+ type PatchPrototype = Record<PropertyKey, unknown>;
46
+
47
+ type LayoutComponent = {
48
+ render: (...args: unknown[]) => unknown;
49
+ invalidate?: (...args: unknown[]) => unknown;
50
+ };
51
+
52
+ const forwardingComponents = new WeakMap<object, LayoutComponent>();
53
+
54
+ function forwardingLayoutComponent(component: object): LayoutComponent | undefined {
55
+ const cached = forwardingComponents.get(component);
56
+ if (cached) return cached;
57
+ const render = (component as Partial<LayoutComponent>).render;
58
+ if (typeof render !== "function") return undefined;
59
+ try {
60
+ const wrapper: LayoutComponent = {
61
+ render: (...args: unknown[]) => {
62
+ const rows = Reflect.apply(render, component, args);
63
+ return Array.isArray(rows) && rows.length === 1 ? ["", rows[0]] : rows;
64
+ },
65
+ invalidate: (...args: unknown[]) => {
66
+ const invalidate = (component as Partial<LayoutComponent>).invalidate;
67
+ return typeof invalidate === "function"
68
+ ? Reflect.apply(invalidate, component, args)
69
+ : undefined;
70
+ },
71
+ };
72
+ forwardingComponents.set(component, wrapper);
73
+ return wrapper;
74
+ } catch {
75
+ return undefined;
76
+ }
77
+ }
78
+
79
+ export type AccentRailLayoutPatchTarget = {
80
+ prototype: object;
81
+ version: string;
82
+ canonicalEntrypoint?: string;
83
+ resolvedModulePath?: string;
84
+ localModulePath?: string;
85
+ };
86
+
87
+ export type AccentRailLayoutPatchDiagnostic =
88
+ | "installed"
89
+ | "reused"
90
+ | "displaced"
91
+ | "unsupported-version"
92
+ | "unsupported-shape"
93
+ | "host-module-unavailable";
94
+
95
+ export type AccentRailLayoutPatchInstallation = {
96
+ cleanup: () => void;
97
+ diagnostic: AccentRailLayoutPatchDiagnostic;
98
+ version?: string;
99
+ };
100
+
101
+ export type AccentRailLayoutPatchRetention = "retained" | "stale" | "failed";
102
+
103
+ export async function retainAccentRailLayoutPatchInstallation(
104
+ install: () => Promise<AccentRailLayoutPatchInstallation>,
105
+ isCurrent: () => boolean,
106
+ retain: (installation: AccentRailLayoutPatchInstallation) => void,
107
+ ): Promise<AccentRailLayoutPatchRetention> {
108
+ let installation: AccentRailLayoutPatchInstallation;
109
+ try {
110
+ installation = await install();
111
+ } catch {
112
+ return "failed";
113
+ }
114
+ if (!isCurrent()) {
115
+ installation.cleanup();
116
+ return "stale";
117
+ }
118
+ try {
119
+ retain(installation);
120
+ } catch {
121
+ installation.cleanup();
122
+ return "failed";
123
+ }
124
+ return "retained";
125
+ }
126
+
127
+ function ownMarker(value: unknown): AccentRailEditorMarker | undefined {
128
+ if (!value || typeof value !== "object") return undefined;
129
+ if (!Object.hasOwn(value, ZENTUI_ACCENT_RAIL_LAYOUT_EDITOR)) {
130
+ return undefined;
131
+ }
132
+ const marker = (value as MarkedEditor)[ZENTUI_ACCENT_RAIL_LAYOUT_EDITOR];
133
+ return marker && typeof marker.owner === "symbol" && typeof marker.active === "function"
134
+ ? marker
135
+ : undefined;
136
+ }
137
+
138
+ function entryMarker(entry: StackEntry): AccentRailEditorMarker | undefined {
139
+ const component = entry.component;
140
+ if (!component || typeof component !== "object") return undefined;
141
+ if (!Object.hasOwn(component, "children")) return undefined;
142
+ const children = (component as { children?: unknown }).children;
143
+ if (!Array.isArray(children) || children.length !== 1) return undefined;
144
+ return ownMarker(children[0]);
145
+ }
146
+
147
+ export function adjustAccentRailLayoutNode(
148
+ node: unknown,
149
+ activeOwners: ReadonlySet<symbol>,
150
+ ): unknown {
151
+ if (!node || typeof node !== "object") return node;
152
+ const candidate = node as LayoutNode;
153
+ if (candidate.type !== "vstack" || !Array.isArray(candidate.entries)) return node;
154
+
155
+ const entries = candidate.entries as StackEntry[];
156
+ const matches: number[] = [];
157
+ for (let index = 0; index < entries.length; index += 1) {
158
+ const entry = entries[index];
159
+ if (entry?.minSize !== 3 || entry.shrink !== 1) continue;
160
+ const marker = entryMarker(entry);
161
+ if (!marker || !activeOwners.has(marker.owner)) continue;
162
+ let active = false;
163
+ try {
164
+ active = marker.active() === true;
165
+ } catch {
166
+ return node;
167
+ }
168
+ if (active) matches.push(index);
169
+ }
170
+ if (matches.length !== 1) return node;
171
+
172
+ const match = matches[0];
173
+ const matchedEntry = entries[match];
174
+ if (!matchedEntry?.component || typeof matchedEntry.component !== "object") return node;
175
+ const component = forwardingLayoutComponent(matchedEntry.component);
176
+ if (!component) return node;
177
+ const nextEntries = entries.map((entry, index) =>
178
+ index === match ? { ...entry, component } : entry,
179
+ );
180
+ return { ...candidate, entries: nextEntries };
181
+ }
182
+
183
+ export function markAccentRailLayoutEditor(
184
+ editor: object,
185
+ owner: symbol,
186
+ active: () => boolean,
187
+ ): boolean {
188
+ try {
189
+ Object.defineProperty(editor, ZENTUI_ACCENT_RAIL_LAYOUT_EDITOR, {
190
+ value: { owner, active } satisfies AccentRailEditorMarker,
191
+ configurable: true,
192
+ enumerable: false,
193
+ writable: false,
194
+ });
195
+ return true;
196
+ } catch {
197
+ return false;
198
+ }
199
+ }
200
+
201
+ function registryOn(prototype: PatchPrototype): LayoutPatchRegistry | undefined {
202
+ const value = prototype[ZENTUI_ACCENT_RAIL_LAYOUT_REGISTRY];
203
+ if (!value || typeof value !== "object") return undefined;
204
+ const registry = value as Partial<LayoutPatchRegistry>;
205
+ return registry.owners instanceof Map && typeof registry.wrapper === "function"
206
+ ? (value as LayoutPatchRegistry)
207
+ : undefined;
208
+ }
209
+
210
+ function discoverLayoutMethod(prototype: PatchPrototype): symbol | undefined {
211
+ const symbols = new Set<PropertyKey>([LAYOUT_NODE, ...Object.getOwnPropertySymbols(prototype)]);
212
+ for (const key of symbols) {
213
+ const method = prototype[key];
214
+ if (typeof method !== "function") continue;
215
+ try {
216
+ const receiver = Object.create(prototype) as Record<PropertyKey, unknown>;
217
+ receiver.entries = [];
218
+ receiver.layoutType = "vstack";
219
+ receiver.gap = 0;
220
+ receiver.align = "stretch";
221
+ const node = Reflect.apply(method as LayoutMethod, receiver, []);
222
+ if (
223
+ node &&
224
+ typeof node === "object" &&
225
+ (node as LayoutNode).type === "vstack" &&
226
+ Array.isArray((node as LayoutNode).entries)
227
+ ) {
228
+ return key as symbol;
229
+ }
230
+ } catch {
231
+ // Probe candidates fail open.
232
+ }
233
+ }
234
+ return undefined;
235
+ }
236
+
237
+ function restorePredecessor(prototype: PatchPrototype, registry: LayoutPatchRegistry): void {
238
+ if (prototype[registry.method] !== registry.wrapper) return;
239
+ if (registry.predecessorDescriptor) {
240
+ Object.defineProperty(prototype, registry.method, registry.predecessorDescriptor);
241
+ } else {
242
+ delete prototype[registry.method];
243
+ }
244
+ }
245
+
246
+ function cleanupOwner(
247
+ prototype: PatchPrototype,
248
+ registry: LayoutPatchRegistry,
249
+ owner: symbol,
250
+ ): () => void {
251
+ let cleaned = false;
252
+ return () => {
253
+ if (cleaned) return;
254
+ cleaned = true;
255
+ const count = registry.owners.get(owner) ?? 0;
256
+ if (count <= 1) registry.owners.delete(owner);
257
+ else registry.owners.set(owner, count - 1);
258
+ if (registry.owners.size > 0) return;
259
+
260
+ // Deactivate before restoration. If a successor patch captured and later
261
+ // restores our wrapper, the ownerless wrapper remains an inert pass-through.
262
+ registry.active = false;
263
+ if (registryOn(prototype) !== registry) return;
264
+ restorePredecessor(prototype, registry);
265
+ if (prototype[ZENTUI_ACCENT_RAIL_LAYOUT_REGISTRY] === registry) {
266
+ delete prototype[ZENTUI_ACCENT_RAIL_LAYOUT_REGISTRY];
267
+ }
268
+ };
269
+ }
270
+
271
+ export function installAccentRailLayoutPatchOnTarget(
272
+ target: AccentRailLayoutPatchTarget,
273
+ owner: symbol,
274
+ ): AccentRailLayoutPatchInstallation {
275
+ if (!SUPPORTED_PI_TUI_VERSION.test(target.version)) {
276
+ return { cleanup: () => {}, diagnostic: "unsupported-version", version: target.version };
277
+ }
278
+ const prototype = target.prototype as PatchPrototype;
279
+ const existing = registryOn(prototype);
280
+ if (existing) {
281
+ if (existing.active && prototype[existing.method] === existing.wrapper) {
282
+ existing.owners.set(owner, (existing.owners.get(owner) ?? 0) + 1);
283
+ return {
284
+ cleanup: cleanupOwner(prototype, existing, owner),
285
+ diagnostic: "reused",
286
+ version: target.version,
287
+ };
288
+ }
289
+
290
+ // A later extension displaced the method. Never wrap that successor and
291
+ // never leave our old wrapper active if the successor restores it later.
292
+ existing.active = false;
293
+ if (prototype[existing.method] !== existing.wrapper) {
294
+ if (prototype[ZENTUI_ACCENT_RAIL_LAYOUT_REGISTRY] === existing) {
295
+ delete prototype[ZENTUI_ACCENT_RAIL_LAYOUT_REGISTRY];
296
+ }
297
+ return { cleanup: () => {}, diagnostic: "displaced", version: target.version };
298
+ }
299
+
300
+ // An inactive wrapper restored by a former successor can be safely reduced
301
+ // to its original predecessor before a fresh installation.
302
+ restorePredecessor(prototype, existing);
303
+ if (prototype[ZENTUI_ACCENT_RAIL_LAYOUT_REGISTRY] === existing) {
304
+ delete prototype[ZENTUI_ACCENT_RAIL_LAYOUT_REGISTRY];
305
+ }
306
+ }
307
+
308
+ const method = discoverLayoutMethod(prototype);
309
+ if (!method) {
310
+ return { cleanup: () => {}, diagnostic: "unsupported-shape", version: target.version };
311
+ }
312
+ const predecessorDescriptor = Object.getOwnPropertyDescriptor(prototype, method);
313
+ const predecessor = prototype[method];
314
+ if (typeof predecessor !== "function" || predecessorDescriptor?.configurable === false) {
315
+ return { cleanup: () => {}, diagnostic: "unsupported-shape", version: target.version };
316
+ }
317
+
318
+ const registry: LayoutPatchRegistry = {
319
+ method,
320
+ predecessor: predecessor as LayoutMethod,
321
+ predecessorDescriptor,
322
+ wrapper: () => undefined,
323
+ owners: new Map([[owner, 1]]),
324
+ active: true,
325
+ };
326
+ registry.wrapper = function accentRailLayoutNodePatch(this: unknown, ...args: unknown[]) {
327
+ const node = Reflect.apply(registry.predecessor, this, args);
328
+ if (!registry.active || registry.owners.size === 0) return node;
329
+ try {
330
+ return adjustAccentRailLayoutNode(node, new Set(registry.owners.keys()));
331
+ } catch {
332
+ return node;
333
+ }
334
+ };
335
+ try {
336
+ Object.defineProperty(prototype, method, {
337
+ ...(predecessorDescriptor ?? {
338
+ configurable: true,
339
+ enumerable: false,
340
+ writable: true,
341
+ }),
342
+ value: registry.wrapper,
343
+ });
344
+ Object.defineProperty(prototype, ZENTUI_ACCENT_RAIL_LAYOUT_REGISTRY, {
345
+ value: registry,
346
+ configurable: true,
347
+ enumerable: false,
348
+ });
349
+ } catch {
350
+ registry.active = false;
351
+ restorePredecessor(prototype, registry);
352
+ return { cleanup: () => {}, diagnostic: "unsupported-shape", version: target.version };
353
+ }
354
+ return {
355
+ cleanup: cleanupOwner(prototype, registry, owner),
356
+ diagnostic: "installed",
357
+ version: target.version,
358
+ };
359
+ }
360
+
361
+ type PackageLocation = {
362
+ root: string;
363
+ version: string;
364
+ };
365
+
366
+ function packageLocationFromEntry(
367
+ entry: string,
368
+ expectedName: string,
369
+ ): PackageLocation | undefined {
370
+ const root = parse(entry).root;
371
+ let current = dirname(entry);
372
+ while (current !== root) {
373
+ try {
374
+ const packageJson = JSON.parse(readFileSync(join(current, "package.json"), "utf8")) as {
375
+ name?: unknown;
376
+ version?: unknown;
377
+ };
378
+ if (packageJson.name === expectedName && typeof packageJson.version === "string") {
379
+ return { root: current, version: packageJson.version };
380
+ }
381
+ } catch {
382
+ // Continue walking to the package root.
383
+ }
384
+ current = dirname(current);
385
+ }
386
+ return undefined;
387
+ }
388
+
389
+ function canonicalFile(path: string): string | undefined {
390
+ try {
391
+ const canonical = realpathSync(path);
392
+ return statSync(canonical).isFile() ? canonical : undefined;
393
+ } catch {
394
+ return undefined;
395
+ }
396
+ }
397
+
398
+ function localPiTuiPath(): string | undefined {
399
+ try {
400
+ return canonicalFile(createRequire(import.meta.url).resolve("@earendil-works/pi-tui"));
401
+ } catch {
402
+ return undefined;
403
+ }
404
+ }
405
+
406
+ export async function discoverAccentRailLayoutPatchTargetFromEntrypoint(
407
+ entrypoint: string,
408
+ ): Promise<AccentRailLayoutPatchTarget | undefined> {
409
+ try {
410
+ const canonicalEntrypoint = canonicalFile(entrypoint);
411
+ if (!canonicalEntrypoint) return undefined;
412
+ if (!packageLocationFromEntry(canonicalEntrypoint, "@earendil-works/pi-coding-agent")) {
413
+ return undefined;
414
+ }
415
+
416
+ const hostRequire = createRequire(canonicalEntrypoint);
417
+ const unresolved = hostRequire.resolve("@earendil-works/pi-tui");
418
+ const resolvedModulePath = canonicalFile(unresolved);
419
+ if (!resolvedModulePath) return undefined;
420
+ const packageLocation = packageLocationFromEntry(resolvedModulePath, "@earendil-works/pi-tui");
421
+ if (!packageLocation || !SUPPORTED_PI_TUI_VERSION.test(packageLocation.version)) {
422
+ return undefined;
423
+ }
424
+
425
+ const module = (await import(pathToFileURL(resolvedModulePath).href)) as {
426
+ VStack?: { prototype?: object };
427
+ };
428
+ const prototype = module.VStack?.prototype;
429
+ if (!prototype || !discoverLayoutMethod(prototype as PatchPrototype)) return undefined;
430
+
431
+ return {
432
+ prototype,
433
+ version: packageLocation.version,
434
+ canonicalEntrypoint,
435
+ resolvedModulePath,
436
+ localModulePath: localPiTuiPath(),
437
+ };
438
+ } catch {
439
+ return undefined;
440
+ }
441
+ }
442
+
443
+ export async function discoverHostAccentRailLayoutPatchTarget(): Promise<
444
+ AccentRailLayoutPatchTarget | undefined
445
+ > {
446
+ const entrypoint = process.argv[1];
447
+ return entrypoint ? discoverAccentRailLayoutPatchTargetFromEntrypoint(entrypoint) : undefined;
448
+ }
449
+
450
+ export async function installHostAccentRailLayoutPatch(
451
+ owner: symbol,
452
+ ): Promise<AccentRailLayoutPatchInstallation> {
453
+ const target = await discoverHostAccentRailLayoutPatchTarget();
454
+ return target
455
+ ? installAccentRailLayoutPatchOnTarget(target, owner)
456
+ : { cleanup: () => {}, diagnostic: "host-module-unavailable" };
457
+ }
@@ -0,0 +1,157 @@
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
2
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
+
4
+ export type CompletionRowsOptions = {
5
+ lines: string[];
6
+ width: number;
7
+ theme: Theme;
8
+ selectedPrefix: string;
9
+ ownedBackground: boolean;
10
+ };
11
+
12
+ export type CompletionPaletteOptions = {
13
+ lines: string[];
14
+ width: number;
15
+ theme: Theme;
16
+ renderSeparator: (text: string) => string;
17
+ ownedBackground: boolean;
18
+ };
19
+
20
+ const SGR_SEQUENCE = /(?:\x1b\[|\u009b)([0-9:;]*)m/g;
21
+
22
+ function normalizeC1Csi(content: string): string {
23
+ return content.replace(/\u009b([0-?]*[ -/]*[@-~])/g, "\x1b[$1");
24
+ }
25
+
26
+ function sgrBackgroundAction(parameters: string): "reset" | "set" | undefined {
27
+ let action: "reset" | "set" | undefined;
28
+ const values = (parameters || "0").split(";");
29
+ for (let index = 0; index < values.length; index++) {
30
+ const parameter = values[index] ?? "";
31
+ const primary = parameter === "" ? 0 : Number.parseInt(parameter.split(":", 1)[0] ?? "", 10);
32
+ if (primary === 38 || primary === 48 || primary === 58) {
33
+ if (primary === 48) action = "set";
34
+ if (!parameter.includes(":")) {
35
+ const mode = Number.parseInt(values[index + 1] ?? "", 10);
36
+ if (mode === 5) index += 2;
37
+ else if (mode === 2) index += 4;
38
+ }
39
+ continue;
40
+ }
41
+ if (primary === 0 || primary === 49) action = "reset";
42
+ else if ((primary >= 40 && primary <= 47) || (primary >= 100 && primary <= 107)) action = "set";
43
+ }
44
+ return action;
45
+ }
46
+
47
+ function restoresOwnedBackground(sequence: string): boolean {
48
+ const parameters = sequence.slice(sequence.startsWith("\u009b") ? 1 : 2, -1);
49
+ return sgrBackgroundAction(parameters) === "reset";
50
+ }
51
+
52
+ function truncateTerminalLine(content: string, width: number): string {
53
+ const safeWidth = Math.max(0, width);
54
+ const normalized = normalizeC1Csi(content);
55
+ return visibleWidth(normalized) <= safeWidth
56
+ ? content
57
+ : truncateToWidth(normalized, safeWidth, "");
58
+ }
59
+
60
+ export function fillTerminalLine(content: string, width: number): string {
61
+ const truncated = truncateTerminalLine(content, width);
62
+ const renderedWidth = visibleWidth(normalizeC1Csi(truncated));
63
+ return `${truncated}${" ".repeat(Math.max(0, width - renderedWidth))}`;
64
+ }
65
+
66
+ export function applyOwnedSurfaceBackground(theme: Theme, text: string): string {
67
+ try {
68
+ const background = theme.getBgAnsi("userMessageBg");
69
+ const restored = text.replace(SGR_SEQUENCE, (sequence) =>
70
+ restoresOwnedBackground(sequence) ? `${sequence}${background}` : sequence,
71
+ );
72
+ return `${background}${restored}\x1b[49m`;
73
+ } catch {
74
+ try {
75
+ let cursor = 0;
76
+ let rendered = "";
77
+ for (const match of text.matchAll(SGR_SEQUENCE)) {
78
+ if (!restoresOwnedBackground(match[0])) continue;
79
+ const before = text.slice(cursor, match.index);
80
+ if (before) rendered += theme.bg("userMessageBg", before);
81
+ rendered += match[0];
82
+ cursor = (match.index ?? 0) + match[0].length;
83
+ }
84
+ const trailing = text.slice(cursor);
85
+ if (trailing) rendered += theme.bg("userMessageBg", trailing);
86
+ return rendered || theme.bg("userMessageBg", text);
87
+ } catch {
88
+ return text;
89
+ }
90
+ }
91
+ }
92
+
93
+ export function replaceNativeSelectedPrefix(line: string, replacement: string): string {
94
+ const selectedPrefix = /^((?:(?:\x1b\[|\u009b)[0-9:;]*m)*)→ /;
95
+ const match = line.match(selectedPrefix);
96
+ if (!match) return line;
97
+ return `${replacement}${match[1]}${line.slice(match[0].length)}`;
98
+ }
99
+
100
+ export function isNativeCompletionCountRow(line: string): boolean {
101
+ const withoutSgr = line.replace(/(?:\x1b\[|\u009b)[0-?]*[ -/]*m/g, "");
102
+ return /^\s*\(\d+\/\d+\)\s*$/.test(withoutSgr);
103
+ }
104
+
105
+ export function omitTrailingNativeCompletionCountRow(lines: string[]): string[] {
106
+ return lines.length > 0 && isNativeCompletionCountRow(lines.at(-1) ?? "")
107
+ ? lines.slice(0, -1)
108
+ : lines;
109
+ }
110
+
111
+ export function renderCompletionRows({
112
+ lines,
113
+ width,
114
+ theme,
115
+ selectedPrefix,
116
+ ownedBackground,
117
+ }: CompletionRowsOptions): string[] {
118
+ return lines.map((line) => {
119
+ const content = fillTerminalLine(replaceNativeSelectedPrefix(line, selectedPrefix), width);
120
+ const surfaced = ownedBackground ? applyOwnedSurfaceBackground(theme, content) : content;
121
+ return truncateTerminalLine(surfaced, width);
122
+ });
123
+ }
124
+
125
+ function helpLabel(width: number): string {
126
+ const labels = [
127
+ " ↑↓ Navigate Enter Use Esc Close",
128
+ " ↑↓ Nav Enter Use Esc",
129
+ " ↑↓ Enter Esc",
130
+ ];
131
+ return labels.find((label) => visibleWidth(label) <= width) ?? labels.at(-1) ?? "";
132
+ }
133
+
134
+ /** Full-width, provenance-preserving completion shell for Opencode editor styles. */
135
+ export function renderCompletionPalette({
136
+ lines,
137
+ width,
138
+ theme,
139
+ renderSeparator,
140
+ ownedBackground,
141
+ }: CompletionPaletteOptions): string[] {
142
+ const resultLines = omitTrailingNativeCompletionCountRow(lines);
143
+ if (resultLines.length === 0) return [];
144
+ const safeWidth = Math.max(0, width);
145
+ const rows = renderCompletionRows({
146
+ lines: resultLines,
147
+ width: safeWidth,
148
+ theme,
149
+ selectedPrefix: " ",
150
+ ownedBackground,
151
+ });
152
+ const separator = renderSeparator("─".repeat(safeWidth));
153
+ if (safeWidth < 3) return [...rows, truncateToWidth(separator, safeWidth, "")];
154
+ const helpText = fillTerminalLine(helpLabel(safeWidth), safeWidth);
155
+ const help = ownedBackground ? applyOwnedSurfaceBackground(theme, helpText) : helpText;
156
+ return [...rows, truncateToWidth(separator, safeWidth, ""), truncateToWidth(help, safeWidth, "")];
157
+ }