@selesai/code 0.9.14 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +79 -29
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +3 -4
  6. package/dist/extensions/pi-zentui/.editorconfig +12 -0
  7. package/dist/extensions/pi-zentui/.github/CODEOWNERS +1 -0
  8. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/bug_report.yml +110 -0
  9. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config.yml +8 -0
  10. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config_question.yml +56 -0
  11. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
  12. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/rendering_issue.yml +106 -0
  13. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/runtime_detection.yml +103 -0
  14. package/dist/extensions/pi-zentui/.github/dependabot.yml +47 -0
  15. package/dist/extensions/pi-zentui/.github/pull_request_template.md +34 -0
  16. package/dist/extensions/pi-zentui/.github/release.yml +18 -0
  17. package/dist/extensions/pi-zentui/.github/workflows/ci.yml +79 -0
  18. package/dist/extensions/pi-zentui/.github/workflows/publish.yml +63 -0
  19. package/dist/extensions/pi-zentui/AGENTS.md +107 -0
  20. package/dist/extensions/pi-zentui/CONTRIBUTING.md +68 -0
  21. package/dist/extensions/pi-zentui/LICENSE +21 -0
  22. package/dist/extensions/pi-zentui/README.md +188 -0
  23. package/dist/extensions/pi-zentui/SECURITY.md +18 -0
  24. package/dist/extensions/pi-zentui/assets/main-cover.png +0 -0
  25. package/dist/extensions/pi-zentui/assets/screenshots/editor-accent-rail.png +0 -0
  26. package/dist/extensions/pi-zentui/assets/screenshots/editor-minimalist.png +0 -0
  27. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode-copy-friendly.png +0 -0
  28. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode.png +0 -0
  29. package/dist/extensions/pi-zentui/assets/screenshots/user-message-compact.png +0 -0
  30. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed-copy-friendly.png +0 -0
  31. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed.png +0 -0
  32. package/dist/extensions/pi-zentui/assets/screenshots/user-message-labeled.png +0 -0
  33. package/dist/extensions/pi-zentui/assets/zentui.png +0 -0
  34. package/dist/extensions/pi-zentui/biome.json +31 -0
  35. package/dist/extensions/pi-zentui/docs/configuration.md +509 -0
  36. package/dist/extensions/pi-zentui/docs/footer-format.md +162 -0
  37. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +99 -0
  38. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +457 -0
  39. package/dist/extensions/pi-zentui/extensions/zentui/completion-menu.ts +157 -0
  40. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +2100 -0
  41. package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +321 -0
  42. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +73 -0
  43. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +89 -0
  44. package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +364 -0
  45. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +122 -0
  46. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +812 -0
  47. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +568 -0
  48. package/dist/extensions/pi-zentui/extensions/zentui/git.ts +384 -0
  49. package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +252 -0
  50. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +1406 -0
  51. package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +1049 -0
  52. package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +75 -0
  53. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +463 -0
  54. package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +651 -0
  55. package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +120 -0
  56. package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +59 -0
  57. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +166 -0
  58. package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +98 -0
  59. package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +841 -0
  60. package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +93 -0
  61. package/dist/extensions/pi-zentui/extensions/zentui/session-lifecycle.ts +60 -0
  62. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1796 -0
  63. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +128 -0
  64. package/dist/extensions/pi-zentui/extensions/zentui/state.ts +94 -0
  65. package/dist/extensions/pi-zentui/extensions/zentui/style.ts +424 -0
  66. package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +82 -0
  67. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +1290 -0
  68. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +248 -0
  69. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +215 -0
  70. package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +206 -0
  71. package/dist/extensions/pi-zentui/extensions/zentui/working-line-messages.ts +24 -0
  72. package/dist/extensions/pi-zentui/extensions/zentui/working-line-spinners.ts +54 -0
  73. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +1433 -0
  74. package/dist/extensions/pi-zentui/package-lock.json +5311 -0
  75. package/dist/extensions/pi-zentui/package.json +60 -0
  76. package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +232 -0
  77. package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +669 -0
  78. package/dist/extensions/pi-zentui/test/completion-menu.test.ts +265 -0
  79. package/dist/extensions/pi-zentui/test/config.test.ts +3119 -0
  80. package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +405 -0
  81. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +260 -0
  82. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +1286 -0
  83. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +7196 -0
  84. package/dist/extensions/pi-zentui/test/extension-status.test.ts +182 -0
  85. package/dist/extensions/pi-zentui/test/fixtures/starship-nerd-font-symbols.toml +172 -0
  86. package/dist/extensions/pi-zentui/test/footer-format.test.ts +431 -0
  87. package/dist/extensions/pi-zentui/test/footer-layout.test.ts +113 -0
  88. package/dist/extensions/pi-zentui/test/footer-repository-path.test.ts +123 -0
  89. package/dist/extensions/pi-zentui/test/format.test.ts +865 -0
  90. package/dist/extensions/pi-zentui/test/git.test.ts +212 -0
  91. package/dist/extensions/pi-zentui/test/icons.test.ts +69 -0
  92. package/dist/extensions/pi-zentui/test/interaction-summary.test.ts +1402 -0
  93. package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +378 -0
  94. package/dist/extensions/pi-zentui/test/live-context.test.ts +139 -0
  95. package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +811 -0
  96. package/dist/extensions/pi-zentui/test/package-version.test.ts +321 -0
  97. package/dist/extensions/pi-zentui/test/project-refresh.test.ts +250 -0
  98. package/dist/extensions/pi-zentui/test/project-state.test.ts +159 -0
  99. package/dist/extensions/pi-zentui/test/repository-root.test.ts +108 -0
  100. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +825 -0
  101. package/dist/extensions/pi-zentui/test/runtime.test.ts +183 -0
  102. package/dist/extensions/pi-zentui/test/session-lifecycle.test.ts +40 -0
  103. package/dist/extensions/pi-zentui/test/settings-command.test.ts +1381 -0
  104. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +347 -0
  105. package/dist/extensions/pi-zentui/test/state.test.ts +84 -0
  106. package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +276 -0
  107. package/dist/extensions/pi-zentui/test/telemetry.test.ts +105 -0
  108. package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +327 -0
  109. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +164 -0
  110. package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +292 -0
  111. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +730 -0
  112. package/dist/extensions/pi-zentui/test/working-line-messages.test.ts +34 -0
  113. package/dist/extensions/pi-zentui/test/working-line.test.ts +1990 -0
  114. package/dist/extensions/pi-zentui/tsconfig.json +16 -0
  115. package/dist/extensions/tps.test.ts +38 -0
  116. package/dist/extensions/{pi-powerline-footer/tps.ts → tps.ts} +4 -0
  117. package/dist/skills/design-references/SKILL.md +31 -0
  118. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  119. package/dist/themes/adventure.json +84 -0
  120. package/dist/themes/adwaita-dark.json +84 -0
  121. package/dist/themes/arcoiris.json +84 -0
  122. package/dist/themes/arthur.json +84 -0
  123. package/dist/themes/atom.json +84 -0
  124. package/dist/themes/aura.json +84 -0
  125. package/dist/themes/black-metal-bathory.json +84 -0
  126. package/dist/themes/black-metal-burzum.json +84 -0
  127. package/dist/themes/black-metal-khold.json +84 -0
  128. package/dist/themes/box.json +84 -0
  129. package/dist/themes/brogrammer.json +84 -0
  130. package/dist/themes/carbonfox.json +84 -0
  131. package/dist/themes/catppuccin-mocha.json +84 -0
  132. package/dist/themes/citruszest.json +84 -0
  133. package/dist/themes/cursor-dark.json +84 -0
  134. package/dist/themes/cutie-pro.json +84 -0
  135. package/dist/themes/dark-modern.json +84 -0
  136. package/dist/themes/dark-pastel.json +84 -0
  137. package/dist/themes/dimmed-monokai.json +84 -0
  138. package/dist/themes/doom-peacock.json +84 -0
  139. package/dist/themes/dracula-plus.json +84 -0
  140. package/dist/themes/dracula.json +80 -0
  141. package/dist/themes/earthsong.json +84 -0
  142. package/dist/themes/everforest-dark-hard.json +84 -0
  143. package/dist/themes/everforest-dark.json +80 -0
  144. package/dist/themes/fahrenheit.json +84 -0
  145. package/dist/themes/flatland.json +84 -0
  146. package/dist/themes/flexoki-dark.json +84 -0
  147. package/dist/themes/front-end-delight.json +84 -0
  148. package/dist/themes/fun-forrest.json +84 -0
  149. package/dist/themes/galizur.json +84 -0
  150. package/dist/themes/github-dark-colorblind.json +84 -0
  151. package/dist/themes/github-dark-high-contrast.json +84 -0
  152. package/dist/themes/glacier.json +84 -0
  153. package/dist/themes/gruber-darker.json +84 -0
  154. package/dist/themes/gruvbox-dark-hard.json +84 -0
  155. package/dist/themes/gruvbox-dark.json +84 -0
  156. package/dist/themes/gruvbox-material.json +84 -0
  157. package/dist/themes/guezwhoz.json +84 -0
  158. package/dist/themes/hacktober.json +84 -0
  159. package/dist/themes/hardcore.json +84 -0
  160. package/dist/themes/havn-skumring.json +84 -0
  161. package/dist/themes/ic-orange-ppl.json +84 -0
  162. package/dist/themes/iterm2-smoooooth.json +84 -0
  163. package/dist/themes/iterm2-tango-dark.json +84 -0
  164. package/dist/themes/japanesque.json +84 -0
  165. package/dist/themes/jellybeans.json +84 -0
  166. package/dist/themes/kanagawa-wave.json +84 -0
  167. package/dist/themes/kurokula.json +84 -0
  168. package/dist/themes/later-this-evening.json +84 -0
  169. package/dist/themes/lovelace.json +84 -0
  170. package/dist/themes/material-darker.json +84 -0
  171. package/dist/themes/matte-black.json +84 -0
  172. package/dist/themes/mellow.json +84 -0
  173. package/dist/themes/miasma.json +84 -0
  174. package/dist/themes/nord.json +80 -0
  175. package/dist/themes/nvim-dark.json +84 -0
  176. package/dist/themes/one-dark.json +80 -0
  177. package/dist/themes/popping-and-locking.json +84 -0
  178. package/dist/themes/rose-pine.json +80 -0
  179. package/dist/themes/sea-shells.json +84 -0
  180. package/dist/themes/sleepy-hollow.json +84 -0
  181. package/dist/themes/smyck.json +84 -0
  182. package/dist/themes/solarized-dark.json +80 -0
  183. package/dist/themes/tokyo-night.json +80 -0
  184. package/dist/themes/tomorrow-night-bright.json +84 -0
  185. package/dist/themes/tomorrow-night-burns.json +84 -0
  186. package/dist/themes/tomorrow-night.json +84 -0
  187. package/dist/themes/twilight.json +84 -0
  188. package/dist/themes/vague.json +84 -0
  189. package/dist/themes/vesper.json +84 -0
  190. package/dist/themes/xcode-dark-hc.json +84 -0
  191. package/docs/custom-provider.md +0 -1
  192. package/docs/plans/auto-model-routing-extension.md +490 -0
  193. package/package.json +3 -3
  194. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  195. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  196. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  197. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  198. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  199. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  200. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  201. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  202. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  203. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  204. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  205. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  206. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  207. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  208. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  209. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  210. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  211. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  212. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  213. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  214. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  215. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  216. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  217. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  218. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  219. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  220. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  221. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  222. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  223. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  224. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  225. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  226. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  227. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  228. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  229. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  230. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  231. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  232. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  233. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  234. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  235. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  236. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  237. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  238. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  239. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  240. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  241. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  242. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  243. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  244. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  245. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  246. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  247. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  248. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  249. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  250. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  251. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  252. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  253. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  254. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  255. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  256. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  257. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  258. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  259. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  260. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  261. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  262. package/dist/extensions/preview-tools-disabled.ts +0 -11
  263. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -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
- }