@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
@@ -1,241 +0,0 @@
1
- /**
2
- * Theme system for powerline-footer
3
- *
4
- * Colors are resolved in order:
5
- * 1. User overrides from theme.json (if exists)
6
- * 2. Preset colors
7
- * 3. Default colors
8
- */
9
-
10
- import type { Theme, ThemeColor } from "@selesai/code";
11
- import { existsSync, readFileSync } from "node:fs";
12
- import { join, dirname } from "node:path";
13
- import { fileURLToPath } from "node:url";
14
- import { getAgentPath } from "./paths.ts";
15
- import type { ColorScheme, ColorValue, SemanticColor, ThemeLike } from "./types.ts";
16
-
17
- export interface PowerlineThemeConfig {
18
- colors?: unknown;
19
- icons?: unknown;
20
- }
21
-
22
- // Default color scheme (uses pi theme colors)
23
- const DEFAULT_COLORS: Required<ColorScheme> = {
24
- model: "#d787af", // Pink/mauve (matching original colors.ts)
25
- shellMode: "accent",
26
- path: "#00afaf", // Teal/cyan (matching original colors.ts)
27
- gitDirty: "warning",
28
- gitClean: "success",
29
- thinking: "thinkingOff",
30
- thinkingMinimal: "thinkingMinimal",
31
- thinkingLow: "thinkingLow",
32
- thinkingMedium: "thinkingMedium",
33
- context: "dim",
34
- contextWarn: "warning",
35
- contextError: "error",
36
- cost: "text",
37
- tokens: "muted",
38
- queue: "accent",
39
- separator: "dim",
40
- border: "borderMuted",
41
- };
42
-
43
- // Rainbow colors for high thinking levels
44
- const RAINBOW_COLORS = [
45
- "#b281d6", "#d787af", "#febc38", "#e4c00f",
46
- "#89d281", "#00afaf", "#178fb9", "#b281d6",
47
- ];
48
-
49
- // Cache for user theme overrides
50
- let userThemeCache: ColorScheme | null = null;
51
- let userThemeCacheTime = 0;
52
- let userThemeCacheKey = "";
53
- let themeConfigCache: PowerlineThemeConfig | null = null;
54
- let themeConfigCacheTime = 0;
55
- let themeConfigCacheKey = "";
56
- const CACHE_TTL = 5000; // 5 seconds
57
- const warnedInvalidThemeColors = new Set<string>();
58
-
59
- function isRecord(value: unknown): value is Record<string, unknown> {
60
- return typeof value === "object" && value !== null && !Array.isArray(value);
61
- }
62
-
63
- function sanitizeUserThemeOverrides(value: unknown): ColorScheme {
64
- if (!isRecord(value)) {
65
- return {};
66
- }
67
-
68
- const sanitized: ColorScheme = {};
69
- for (const [key, rawColor] of Object.entries(value)) {
70
- if (!Object.prototype.hasOwnProperty.call(DEFAULT_COLORS, key)) {
71
- continue;
72
- }
73
- if (typeof rawColor !== "string") {
74
- continue;
75
- }
76
-
77
- const color = rawColor.trim();
78
- if (!color) {
79
- continue;
80
- }
81
-
82
- sanitized[key as SemanticColor] = color as ColorValue;
83
- }
84
-
85
- return sanitized;
86
- }
87
-
88
- /**
89
- * Get theme.json lookup paths.
90
- */
91
- function getThemePaths(): string[] {
92
- const extDir = dirname(fileURLToPath(import.meta.url));
93
- return Array.from(new Set([
94
- getAgentPath("extensions", "powerline-footer", "theme.json"),
95
- join(extDir, "theme.json"),
96
- ]));
97
- }
98
-
99
- /**
100
- * Load user theme config from theme.json.
101
- */
102
- export function loadThemeConfig(): PowerlineThemeConfig {
103
- const now = Date.now();
104
- const themePaths = getThemePaths();
105
- const cacheKey = themePaths.join("\0");
106
- if (themeConfigCache && themeConfigCacheKey === cacheKey && now - themeConfigCacheTime < CACHE_TTL) {
107
- return themeConfigCache;
108
- }
109
-
110
- for (const themePath of themePaths) {
111
- try {
112
- if (existsSync(themePath)) {
113
- const content = readFileSync(themePath, "utf-8");
114
- const parsed = JSON.parse(content);
115
- themeConfigCache = isRecord(parsed) ? parsed : {};
116
- themeConfigCacheTime = now;
117
- themeConfigCacheKey = cacheKey;
118
- return themeConfigCache;
119
- }
120
- } catch (error) {
121
- // Theme overrides are optional. If the file is unreadable or malformed,
122
- // keep rendering with built-in defaults instead of breaking the footer.
123
- console.debug(`[powerline-theme] Failed to load ${themePath}:`, error);
124
- }
125
- }
126
-
127
- themeConfigCache = {};
128
- themeConfigCacheTime = now;
129
- themeConfigCacheKey = cacheKey;
130
- return themeConfigCache;
131
- }
132
-
133
- function loadUserTheme(): ColorScheme {
134
- const now = Date.now();
135
- const cacheKey = getThemePaths().join("\0");
136
- if (userThemeCache && userThemeCacheKey === cacheKey && now - userThemeCacheTime < CACHE_TTL) {
137
- return userThemeCache;
138
- }
139
-
140
- userThemeCache = sanitizeUserThemeOverrides(loadThemeConfig().colors);
141
- userThemeCacheTime = now;
142
- userThemeCacheKey = cacheKey;
143
- return userThemeCache;
144
- }
145
-
146
- /**
147
- * Resolve a semantic color to an actual color value
148
- */
149
- export function resolveColor(
150
- semantic: SemanticColor,
151
- presetColors?: ColorScheme
152
- ): ColorValue {
153
- const userTheme = loadUserTheme();
154
-
155
- // Priority: user overrides > preset colors > defaults
156
- return userTheme[semantic]
157
- ?? presetColors?.[semantic]
158
- ?? DEFAULT_COLORS[semantic];
159
- }
160
-
161
- /**
162
- * Check if a color value is a hex color
163
- */
164
- function isHexColor(color: ColorValue): color is `#${string}` {
165
- return typeof color === "string" && /^#[0-9a-fA-F]{6}$/.test(color);
166
- }
167
-
168
- /**
169
- * Convert hex color to ANSI escape code
170
- */
171
- function hexToAnsi(hex: string): string {
172
- const h = hex.replace("#", "");
173
- const r = parseInt(h.slice(0, 2), 16);
174
- const g = parseInt(h.slice(2, 4), 16);
175
- const b = parseInt(h.slice(4, 6), 16);
176
- return `\x1b[38;2;${r};${g};${b}m`;
177
- }
178
-
179
- /**
180
- * Apply a color to text using the pi theme or custom hex
181
- */
182
- export function applyColor(
183
- theme: ThemeLike,
184
- color: ColorValue,
185
- text: string
186
- ): string {
187
- if (isHexColor(color)) {
188
- return `${hexToAnsi(color)}${text}\x1b[0m`;
189
- }
190
-
191
- try {
192
- return theme.fg(color as ThemeColor, text);
193
- } catch (error) {
194
- const key = String(color);
195
- if (!warnedInvalidThemeColors.has(key)) {
196
- warnedInvalidThemeColors.add(key);
197
- if (warnedInvalidThemeColors.size > 200) {
198
- warnedInvalidThemeColors.clear();
199
- }
200
- console.debug(`[powerline-theme] Invalid theme color "${key}"; falling back to "text".`, error);
201
- }
202
- return theme.fg("text", text);
203
- }
204
- }
205
-
206
- /**
207
- * Apply a semantic color to text
208
- */
209
- export function fg(
210
- theme: ThemeLike,
211
- semantic: SemanticColor,
212
- text: string,
213
- presetColors?: ColorScheme
214
- ): string {
215
- const color = resolveColor(semantic, presetColors);
216
- return applyColor(theme, color, text);
217
- }
218
-
219
- /**
220
- * Apply rainbow gradient to text (for high thinking levels)
221
- */
222
- export function rainbow(text: string): string {
223
- let result = "";
224
- let colorIndex = 0;
225
- for (const char of text) {
226
- if (char === " " || char === ":") {
227
- result += char;
228
- } else {
229
- result += hexToAnsi(RAINBOW_COLORS[colorIndex % RAINBOW_COLORS.length]) + char;
230
- colorIndex++;
231
- }
232
- }
233
- return result + "\x1b[0m";
234
- }
235
-
236
- /**
237
- * Get the default color scheme
238
- */
239
- export function getDefaultColors(): Required<ColorScheme> {
240
- return { ...DEFAULT_COLORS };
241
- }
@@ -1,304 +0,0 @@
1
- import type { AssistantMessage } from "@earendil-works/pi-ai";
2
-
3
- type SessionAssistantUsage = AssistantMessage["usage"];
4
-
5
- export interface SessionTokenStats {
6
- input: number;
7
- output: number;
8
- cacheRead: number;
9
- cacheWrite: number;
10
- cost: number;
11
- subagentCost: number;
12
- lastAssistant: AssistantMessage | undefined;
13
- thinkingLevelFromSession: string | null;
14
- }
15
-
16
- function isRecord(value: unknown): value is Record<string, unknown> {
17
- return typeof value === "object" && value !== null && !Array.isArray(value);
18
- }
19
-
20
- function hasSessionAssistantUsage(value: unknown): value is SessionAssistantUsage {
21
- if (!isRecord(value)) {
22
- return false;
23
- }
24
-
25
- if (
26
- typeof value.input !== "number" ||
27
- typeof value.output !== "number" ||
28
- typeof value.cacheRead !== "number" ||
29
- typeof value.cacheWrite !== "number"
30
- ) {
31
- return false;
32
- }
33
-
34
- return isRecord(value.cost) && typeof value.cost.total === "number";
35
- }
36
-
37
- function isSessionAssistantMessage(value: unknown): value is AssistantMessage {
38
- return isRecord(value)
39
- && value.role === "assistant"
40
- && hasSessionAssistantUsage(value.usage)
41
- && (value.stopReason === undefined || typeof value.stopReason === "string");
42
- }
43
-
44
- function getUsageTokenTotal(usage: SessionAssistantUsage): number {
45
- const totalTokens = "totalTokens" in usage && typeof usage.totalTokens === "number" ? usage.totalTokens : 0;
46
- return totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
47
- }
48
-
49
- // Matches pi-subagents' internal SLASH_RESULT_TYPE custom_message marker.
50
- const SUBAGENT_SLASH_RESULT_TYPE = "subagent-slash-result";
51
-
52
- function subagentDetailsFromSessionEntry(e: Record<string, unknown>): { results: unknown[] } | undefined {
53
- if (e.type === "custom_message" && e.customType === SUBAGENT_SLASH_RESULT_TYPE) {
54
- const details = isRecord(e.details) ? e.details : undefined;
55
- const result = isRecord(details?.result) ? details.result : undefined;
56
- const inner = isRecord(result?.details) ? result.details : undefined;
57
- return Array.isArray(inner?.results) ? { results: inner.results } : undefined;
58
- }
59
- if (e.type === "message" && isRecord(e.message)) {
60
- const message = e.message as { role?: unknown; toolName?: unknown; details?: unknown };
61
- if (message.role === "toolResult" && message.toolName === "subagent" && isRecord(message.details) && Array.isArray(message.details.results)) {
62
- return { results: message.details.results };
63
- }
64
- }
65
- return undefined;
66
- }
67
-
68
- // Sums subagent child usage cost recorded on a single session entry (parallel/chain/single runs
69
- // launched via the subagent tool or /parallel, /worker etc. slash commands), so the footer can
70
- // show total spend rather than only the interactive parent session's cost.
71
- function extractSubagentResultCost(e: Record<string, unknown>): number {
72
- const details = subagentDetailsFromSessionEntry(e);
73
- if (!details) return 0;
74
- let total = 0;
75
- for (const result of details.results) {
76
- if (!isRecord(result)) continue;
77
- const usage = isRecord(result.usage) ? result.usage : undefined;
78
- if (typeof usage?.cost === "number") total += usage.cost;
79
- }
80
- return total;
81
- }
82
-
83
- /**
84
- * Fingerprint of the fields on a session event that influence the aggregated
85
- * stats. Comparing only the event count is not enough: while streaming, pi
86
- * may update the trailing assistant message in place (usage grows, stopReason
87
- * flips), so the same event reference can carry fresh numbers.
88
- */
89
- function eventStatsSignature(event: unknown): string {
90
- if (!isRecord(event)) {
91
- return "?";
92
- }
93
-
94
- if (event.type === "thinking_level_change") {
95
- return `t:${typeof event.thinkingLevel === "string" ? event.thinkingLevel : ""}`;
96
- }
97
-
98
- const subagentDetails = subagentDetailsFromSessionEntry(event);
99
- if (subagentDetails) {
100
- return `s:${subagentDetails.results.length}:${extractSubagentResultCost(event)}`;
101
- }
102
-
103
- if (event.type === "message" && isRecord(event.message)) {
104
- const message = event.message;
105
- const role = typeof message.role === "string" ? message.role : "";
106
- const stopReason = typeof message.stopReason === "string" ? message.stopReason : "";
107
- if (role === "assistant" && hasSessionAssistantUsage(message.usage)) {
108
- const usage = message.usage;
109
- return `a:${stopReason}:${usage.input}/${usage.output}/${usage.cacheRead}/${usage.cacheWrite}/${usage.cost.total}`;
110
- }
111
- return `m:${role}:${stopReason}`;
112
- }
113
-
114
- return `e:${typeof event.type === "string" ? event.type : "?"}`;
115
- }
116
-
117
- function emptySessionTokenStats(): SessionTokenStats {
118
- return {
119
- input: 0,
120
- output: 0,
121
- cacheRead: 0,
122
- cacheWrite: 0,
123
- cost: 0,
124
- subagentCost: 0,
125
- lastAssistant: undefined,
126
- thinkingLevelFromSession: null,
127
- };
128
- }
129
-
130
- function copySessionTokenStats(stats: SessionTokenStats): SessionTokenStats {
131
- return { ...stats };
132
- }
133
-
134
- function accumulateSessionEvent(stats: SessionTokenStats, event: unknown): void {
135
- if (!isRecord(event)) return;
136
-
137
- if (event.type === "thinking_level_change" && typeof event.thinkingLevel === "string") {
138
- stats.thinkingLevelFromSession = event.thinkingLevel;
139
- }
140
-
141
- stats.subagentCost += extractSubagentResultCost(event);
142
-
143
- if (event.type !== "message" || !isSessionAssistantMessage(event.message)) return;
144
-
145
- const message = event.message;
146
- if (message.stopReason === "error" || message.stopReason === "aborted") return;
147
-
148
- stats.input += message.usage.input;
149
- stats.output += message.usage.output;
150
- stats.cacheRead += message.usage.cacheRead;
151
- stats.cacheWrite += message.usage.cacheWrite;
152
- stats.cost += message.usage.cost.total;
153
- if (getUsageTokenTotal(message.usage) > 0) {
154
- stats.lastAssistant = message;
155
- }
156
- }
157
-
158
- export interface SessionBranchProvider {
159
- getLeafId(): string | null;
160
- getBranch(): readonly unknown[];
161
- }
162
-
163
- function isSessionBranchProvider(value: unknown): value is SessionBranchProvider {
164
- return isRecord(value) && typeof value.getLeafId === "function" && typeof value.getBranch === "function";
165
- }
166
-
167
- /**
168
- * Session branches are immutable for a given leaf. Keep the already-built path
169
- * while streaming updates mutate only its trailing message in place.
170
- */
171
- export class SessionBranchCache {
172
- private provider: SessionBranchProvider | null = null;
173
- private leafId: string | null = null;
174
- private branch: readonly unknown[] = [];
175
-
176
- get(source: unknown): readonly unknown[] {
177
- if (!isSessionBranchProvider(source)) return [];
178
-
179
- const provider = source;
180
- const leafId = provider.getLeafId();
181
- // A reset cache holds provider === null, which never matches a live provider.
182
- if (this.provider !== provider || this.leafId !== leafId) {
183
- this.provider = provider;
184
- this.leafId = leafId;
185
- this.branch = provider.getBranch();
186
- }
187
- return this.branch;
188
- }
189
-
190
- reset(): void {
191
- this.provider = null;
192
- this.leafId = null;
193
- this.branch = [];
194
- }
195
- }
196
-
197
- export function computeSessionTokenStats(sessionEvents: readonly unknown[]): SessionTokenStats {
198
- let input = 0, output = 0, cacheRead = 0, cacheWrite = 0, cost = 0, subagentCost = 0;
199
- let lastAssistant: AssistantMessage | undefined;
200
- let thinkingLevelFromSession: string | null = null;
201
-
202
- for (const e of sessionEvents) {
203
- if (!isRecord(e)) continue;
204
-
205
- if (e.type === "thinking_level_change" && typeof e.thinkingLevel === "string") {
206
- thinkingLevelFromSession = e.thinkingLevel;
207
- }
208
-
209
- subagentCost += extractSubagentResultCost(e);
210
-
211
- if (e.type !== "message" || !isSessionAssistantMessage(e.message)) continue;
212
-
213
- const m = e.message;
214
- if (m.stopReason === "error" || m.stopReason === "aborted") continue;
215
-
216
- input += m.usage.input;
217
- output += m.usage.output;
218
- cacheRead += m.usage.cacheRead;
219
- cacheWrite += m.usage.cacheWrite;
220
- cost += m.usage.cost.total;
221
- if (getUsageTokenTotal(m.usage) > 0) {
222
- lastAssistant = m;
223
- }
224
- }
225
-
226
- return { input, output, cacheRead, cacheWrite, cost, subagentCost, lastAssistant, thinkingLevelFromSession };
227
- }
228
-
229
- /**
230
- * Cache for token counting: avoid re-scanning the full session event list on
231
- * every render (250ms-1s cadence while streaming). Historical session entries
232
- * are append-only, so appended entries are accumulated and an in-place update
233
- * to the trailing streaming entry only recomputes that entry.
234
- */
235
- export class SessionTokenStatsCache {
236
- private eventCount = -1;
237
- private lastEvent: unknown;
238
- private lastSignature = "";
239
- private prefixStats: SessionTokenStats | null = null;
240
- private stats: SessionTokenStats | null = null;
241
-
242
- get(sessionEvents: readonly unknown[]): SessionTokenStats {
243
- const eventCount = sessionEvents.length;
244
- const lastEvent = eventCount > 0 ? sessionEvents[eventCount - 1] : undefined;
245
- const lastSignature = eventStatsSignature(lastEvent);
246
-
247
- if (
248
- this.stats !== null
249
- && this.eventCount === eventCount
250
- && this.lastEvent === lastEvent
251
- && this.lastSignature === lastSignature
252
- ) {
253
- return this.stats;
254
- }
255
-
256
- // getBranch() walks unique parent links, so an identical trailing event at
257
- // the previous count proves the whole previous array is an unchanged prefix.
258
- const previousStats = this.stats;
259
- const canExtendPreviousStats = previousStats !== null
260
- && eventCount > this.eventCount
261
- && (this.eventCount === 0 || (
262
- sessionEvents[this.eventCount - 1] === this.lastEvent
263
- && eventStatsSignature(sessionEvents[this.eventCount - 1]) === this.lastSignature
264
- ));
265
-
266
- let prefixStats: SessionTokenStats;
267
- if (canExtendPreviousStats && previousStats !== null) {
268
- prefixStats = copySessionTokenStats(previousStats);
269
- for (let index = this.eventCount; index < eventCount - 1; index += 1) {
270
- accumulateSessionEvent(prefixStats, sessionEvents[index]);
271
- }
272
- } else if (
273
- this.prefixStats !== null
274
- && this.eventCount === eventCount
275
- && this.lastEvent === lastEvent
276
- ) {
277
- // Same array with an in-place tail mutation: reuse the cached prefix.
278
- prefixStats = this.prefixStats;
279
- } else {
280
- prefixStats = emptySessionTokenStats();
281
- for (let index = 0; index < eventCount - 1; index += 1) {
282
- accumulateSessionEvent(prefixStats, sessionEvents[index]);
283
- }
284
- }
285
-
286
- const stats = copySessionTokenStats(prefixStats);
287
- if (eventCount > 0) accumulateSessionEvent(stats, lastEvent);
288
-
289
- this.eventCount = eventCount;
290
- this.lastEvent = lastEvent;
291
- this.lastSignature = lastSignature;
292
- this.prefixStats = prefixStats;
293
- this.stats = stats;
294
- return stats;
295
- }
296
-
297
- reset(): void {
298
- this.eventCount = -1;
299
- this.lastEvent = undefined;
300
- this.lastSignature = "";
301
- this.prefixStats = null;
302
- this.stats = null;
303
- }
304
- }
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "strict": true,
7
- "noEmit": true,
8
- "skipLibCheck": true,
9
- "types": ["node"],
10
- "allowImportingTsExtensions": true
11
- },
12
- "include": ["*.ts", "bash-mode/**/*.ts", "queue/**/*.ts"]
13
- }