@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
@@ -1,204 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { getAgentPath, getHomeDir } from "../paths.ts";
4
-
5
- interface PersistedHistoryEntry {
6
- command: string;
7
- cwd: string;
8
- timestamp: number;
9
- }
10
-
11
- // mode and ctimeMs are part of the fingerprint so permission/ACL transitions
12
- // (e.g. chmod 000) invalidate the cache even when content is unchanged.
13
- interface FileFingerprint {
14
- ctimeMs: number;
15
- mtimeMs: number;
16
- mode: number;
17
- size: number;
18
- }
19
-
20
- interface CachedHistory<T> extends FileFingerprint {
21
- entries: T;
22
- }
23
-
24
- function matchesFingerprint(cached: FileFingerprint, stat: FileFingerprint): boolean {
25
- return cached.ctimeMs === stat.ctimeMs
26
- && cached.mtimeMs === stat.mtimeMs
27
- && cached.mode === stat.mode
28
- && cached.size === stat.size;
29
- }
30
-
31
- const projectHistoryCache = new Map<string, CachedHistory<PersistedHistoryEntry[]>>();
32
- const globalHistoryCache = new Map<string, CachedHistory<string[]>>();
33
-
34
- function getHistoryDir(): string {
35
- return getAgentPath("powerline-footer", "bash-history");
36
- }
37
-
38
- function projectKey(cwd: string): string {
39
- return cwd.replace(/^[/\\]+|[/\\]+$/g, "").replace(/[\\/]+/g, "-") || "root";
40
- }
41
-
42
- function projectHistoryPath(cwd: string): string {
43
- return join(getHistoryDir(), `${projectKey(cwd)}.json`);
44
- }
45
-
46
- function normalizePersistedEntries(value: unknown): PersistedHistoryEntry[] {
47
- if (!Array.isArray(value)) return [];
48
-
49
- const entries: PersistedHistoryEntry[] = [];
50
- for (const entry of value) {
51
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
52
- const command = typeof entry.command === "string" ? entry.command.trim() : "";
53
- const cwd = typeof entry.cwd === "string" ? entry.cwd.trim() : "";
54
- const timestamp = typeof entry.timestamp === "number" && Number.isFinite(entry.timestamp)
55
- ? entry.timestamp
56
- : 0;
57
- if (!command || !cwd || !timestamp) continue;
58
- entries.push({ command, cwd, timestamp });
59
- }
60
- return entries;
61
- }
62
-
63
- export function readProjectHistory(cwd: string): PersistedHistoryEntry[] {
64
- const filePath = projectHistoryPath(cwd);
65
- if (!existsSync(filePath)) {
66
- projectHistoryCache.delete(filePath);
67
- return [];
68
- }
69
-
70
- try {
71
- const stat = statSync(filePath);
72
- const cached = projectHistoryCache.get(filePath);
73
- if (cached && matchesFingerprint(cached, stat)) {
74
- return cached.entries;
75
- }
76
-
77
- const parsed = JSON.parse(readFileSync(filePath, "utf8"));
78
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return [];
79
- const entries = normalizePersistedEntries((parsed as { entries?: unknown }).entries)
80
- .sort((a, b) => b.timestamp - a.timestamp);
81
- projectHistoryCache.set(filePath, {
82
- ctimeMs: stat.ctimeMs,
83
- mtimeMs: stat.mtimeMs,
84
- mode: stat.mode,
85
- size: stat.size,
86
- entries,
87
- });
88
- return entries;
89
- } catch (error) {
90
- // Project history is a best-effort cache. If it is unreadable or malformed,
91
- // bash mode should keep working instead of failing command entry entirely.
92
- console.debug(`[powerline-footer] Failed to read bash project history from ${filePath}:`, error);
93
- return [];
94
- }
95
- }
96
-
97
- export function appendProjectHistory(cwd: string, command: string, entryCwd: string): void {
98
- const normalizedCommand = command.trim();
99
- if (!normalizedCommand) return;
100
-
101
- const existing = readProjectHistory(cwd);
102
- const next: PersistedHistoryEntry[] = [
103
- { command: normalizedCommand, cwd: entryCwd, timestamp: Date.now() },
104
- ...existing.filter((entry) => entry.command !== normalizedCommand),
105
- ]
106
- .slice(0, 500)
107
- .sort((a, b) => b.timestamp - a.timestamp);
108
-
109
- const filePath = projectHistoryPath(cwd);
110
- projectHistoryCache.delete(filePath);
111
- try {
112
- mkdirSync(dirname(filePath), { recursive: true });
113
- writeFileSync(filePath, JSON.stringify({ version: 1, entries: next }, null, 2) + "\n");
114
- const stat = statSync(filePath);
115
- projectHistoryCache.set(filePath, {
116
- ctimeMs: stat.ctimeMs,
117
- mtimeMs: stat.mtimeMs,
118
- mode: stat.mode,
119
- size: stat.size,
120
- entries: next,
121
- });
122
- } catch (error) {
123
- // History persistence should never block a successful shell command from completing.
124
- console.debug(`[powerline-footer] Failed to persist bash project history to ${filePath}:`, error);
125
- }
126
- }
127
-
128
- function parseZshHistoryLine(line: string): string | null {
129
- const trimmed = line.trim();
130
- if (!trimmed) return null;
131
- if (!trimmed.startsWith(":")) return trimmed;
132
- const parts = trimmed.split(";");
133
- if (parts.length < 2) return null;
134
- return parts.slice(1).join(";").trim() || null;
135
- }
136
-
137
- function parseBashHistory(lines: string[]): string[] {
138
- return lines.map((line) => line.trim()).filter(Boolean);
139
- }
140
-
141
- function parseFishHistory(raw: string): string[] {
142
- const matches = raw.matchAll(/^\s*-\s*cmd:\s*(.+)$/gm);
143
- const commands: string[] = [];
144
- for (const match of matches) {
145
- const command = match[1]?.trim();
146
- if (command) commands.push(command);
147
- }
148
- return commands;
149
- }
150
-
151
- export function readGlobalShellHistory(shellPath: string): string[] {
152
- const shellName = shellPath.split("/").pop()?.toLowerCase() ?? "";
153
- const home = getHomeDir();
154
- const filePath = shellName.includes("fish")
155
- ? join(home, ".local", "share", "fish", "fish_history")
156
- : process.env.HISTFILE || join(home, shellName.includes("zsh") ? ".zsh_history" : ".bash_history");
157
- const cacheKey = `${shellName}\0${filePath}`;
158
-
159
- if (!existsSync(filePath)) {
160
- globalHistoryCache.delete(cacheKey);
161
- return [];
162
- }
163
-
164
- let stat: FileFingerprint | undefined;
165
- try {
166
- stat = statSync(filePath);
167
- const cached = globalHistoryCache.get(cacheKey);
168
- if (cached && matchesFingerprint(cached, stat)) {
169
- return cached.entries;
170
- }
171
-
172
- const raw = readFileSync(filePath, "utf8");
173
- const entries = shellName.includes("zsh")
174
- ? raw.split("\n").map(parseZshHistoryLine).filter((entry): entry is string => Boolean(entry)).reverse()
175
- : shellName.includes("fish")
176
- ? parseFishHistory(raw).reverse()
177
- : parseBashHistory(raw.split("\n")).reverse();
178
- globalHistoryCache.set(cacheKey, { ...stat, entries });
179
- return entries;
180
- } catch (error) {
181
- if (stat) {
182
- globalHistoryCache.set(cacheKey, { ...stat, entries: [] });
183
- console.debug(`[powerline-footer] Failed to read global shell history for ${shellName}:`, error);
184
- }
185
- return [];
186
- }
187
- }
188
-
189
- export function matchHistoryEntries(entries: string[], prefix: string, limit: number): string[] {
190
- const trimmedPrefix = prefix.trim();
191
- const seen = new Set<string>();
192
- const matches: string[] = [];
193
-
194
- for (const rawEntry of entries) {
195
- const entry = rawEntry?.trim();
196
- if (!entry || seen.has(entry)) continue;
197
- if (trimmedPrefix && !entry.startsWith(trimmedPrefix)) continue;
198
- seen.add(entry);
199
- matches.push(entry);
200
- if (matches.length >= limit) break;
201
- }
202
-
203
- return matches;
204
- }
@@ -1,286 +0,0 @@
1
- import { mkdtempSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { basename, join } from "node:path";
4
- import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
5
- import type { BashTranscriptStore } from "./transcript.ts";
6
- import type { ShellSessionState } from "./types.ts";
7
-
8
- const READY_SENTINEL = "__PI_READY__";
9
- const COMMAND_START_SENTINEL = "__PI_CMD_START__";
10
- const COMMAND_DONE_SENTINEL = "__PI_CMD_DONE__";
11
-
12
- function stripAnsi(value: string): string {
13
- return value
14
- .replace(/\x1B\[[0-9;?]*[ -/]*[@-~]/g, "")
15
- .replace(/\x1B\][^\u0007]*(?:\u0007|\x1b\\)/g, "")
16
- .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
17
- }
18
-
19
- function quoteShellArg(value: string): string {
20
- return `'${value.replace(/'/g, `'"'"'`)}'`;
21
- }
22
-
23
- function getCloseExitCode(code: number | null, signal: NodeJS.Signals | null): number {
24
- if (typeof code === "number") {
25
- return code;
26
- }
27
-
28
- if (signal === "SIGINT") {
29
- return 130;
30
- }
31
-
32
- if (signal === "SIGTERM") {
33
- return 143;
34
- }
35
-
36
- if (signal === "SIGKILL") {
37
- return 137;
38
- }
39
-
40
- return 1;
41
- }
42
-
43
- function getShellInitScript(shellName: string): string {
44
- if (shellName.includes("fish")) {
45
- return `
46
- function __pi_eval
47
- set -l __pi_id $argv[1]
48
- set -l __pi_file $argv[2]
49
- echo "${COMMAND_START_SENTINEL}:$__pi_id:$PWD"
50
- source $__pi_file
51
- set -l __pi_status $status
52
- rm -f $__pi_file
53
- echo "${COMMAND_DONE_SENTINEL}:$__pi_id:$__pi_status:$PWD"
54
- end
55
- echo "${READY_SENTINEL}:$PWD"
56
- `;
57
- }
58
-
59
- if (shellName.includes("bash")) {
60
- return `
61
- __pi_eval() {
62
- local __pi_id="$1"
63
- local __pi_file="$2"
64
- printf '%s:%s:%s\n' '${COMMAND_START_SENTINEL}' "$__pi_id" "$PWD"
65
- source "$__pi_file"
66
- local __pi_status=$?
67
- rm -f "$__pi_file"
68
- printf '%s:%s:%s:%s\n' '${COMMAND_DONE_SENTINEL}' "$__pi_id" "$__pi_status" "$PWD"
69
- }
70
- printf '%s:%s\n' '${READY_SENTINEL}' "$PWD"
71
- `;
72
- }
73
-
74
- return `
75
- function __pi_eval() {
76
- local __pi_id="$1"
77
- local __pi_file="$2"
78
- print -r -- "${COMMAND_START_SENTINEL}:$__pi_id:$PWD"
79
- builtin source "$__pi_file"
80
- local __pi_status=$?
81
- rm -f "$__pi_file"
82
- print -r -- "${COMMAND_DONE_SENTINEL}:$__pi_id:$__pi_status:$PWD"
83
- }
84
- print -r -- "${READY_SENTINEL}:$PWD"
85
- `;
86
- }
87
-
88
- export class ManagedShellSession {
89
- private readonly shellPath: string;
90
- private readonly transcript: BashTranscriptStore;
91
- private readonly onStateChange: () => void;
92
- private readonly onCommandSuccess: (command: string, cwd: string) => void;
93
- private process: ChildProcessWithoutNullStreams | null = null;
94
- private readonly tempDir = mkdtempSync(join(tmpdir(), "powerline-bash-mode-"));
95
- private buffer = "";
96
- private commandCounter = 0;
97
- private currentCommandId: string | null = null;
98
- private readyPromise: Promise<void> | null = null;
99
- private readyResolve: (() => void) | null = null;
100
- private readyReject: ((error: Error) => void) | null = null;
101
- private disposed = false;
102
- readonly state: ShellSessionState;
103
-
104
- constructor(
105
- shellPath: string,
106
- cwd: string,
107
- transcript: BashTranscriptStore,
108
- onStateChange: () => void,
109
- onCommandSuccess: (command: string, cwd: string) => void,
110
- ) {
111
- this.shellPath = shellPath;
112
- this.transcript = transcript;
113
- this.onStateChange = onStateChange;
114
- this.onCommandSuccess = onCommandSuccess;
115
- const shellName = basename(shellPath).toLowerCase();
116
- this.state = {
117
- ready: false,
118
- running: false,
119
- shellPath,
120
- shellName,
121
- cwd,
122
- lastExitCode: null,
123
- };
124
- }
125
-
126
- async ensureReady(): Promise<void> {
127
- if (this.state.ready) return;
128
- if (this.readyPromise) return this.readyPromise;
129
-
130
- this.readyPromise = new Promise<void>((resolve, reject) => {
131
- this.readyResolve = resolve;
132
- this.readyReject = reject;
133
- });
134
-
135
- this.process = spawn(this.shellPath, [], {
136
- cwd: this.state.cwd,
137
- env: {
138
- ...process.env,
139
- DISABLE_AUTO_UPDATE: "true",
140
- DISABLE_UPDATE_PROMPT: "true",
141
- },
142
- stdio: ["pipe", "pipe", "pipe"],
143
- detached: true,
144
- });
145
-
146
- this.process.stdout.setEncoding("utf8");
147
- this.process.stderr.setEncoding("utf8");
148
- this.process.stdout.on("data", (chunk) => this.handleChunk(String(chunk)));
149
- this.process.stderr.on("data", (chunk) => this.handleChunk(String(chunk)));
150
- this.process.on("error", (error) => {
151
- if (!this.state.ready) {
152
- this.readyReject?.(error instanceof Error ? error : new Error(String(error)));
153
- }
154
- });
155
- this.process.on("close", (code, signal) => {
156
- const exitCode = getCloseExitCode(code, signal);
157
- if (!this.disposed && !this.state.ready) {
158
- this.readyReject?.(new Error(`Shell failed to start (exit ${exitCode})`));
159
- }
160
-
161
- if (this.currentCommandId) {
162
- this.transcript.finishCommand(this.currentCommandId, exitCode);
163
- this.state.lastExitCode = exitCode;
164
- this.currentCommandId = null;
165
- }
166
-
167
- this.process = null;
168
- this.buffer = "";
169
- this.readyPromise = null;
170
- this.readyResolve = null;
171
- this.readyReject = null;
172
- this.state.ready = false;
173
- this.state.running = false;
174
- this.onStateChange();
175
- });
176
-
177
- this.sendRaw(getShellInitScript(this.state.shellName) + "\n");
178
- return this.readyPromise;
179
- }
180
-
181
- async runCommand(command: string): Promise<void> {
182
- await this.ensureReady();
183
- if (!this.process) {
184
- throw new Error("Shell process not available");
185
- }
186
- if (this.state.running) {
187
- throw new Error("Shell command already running");
188
- }
189
-
190
- const id = `cmd-${++this.commandCounter}`;
191
- const extension = this.state.shellName.includes("fish") ? "fish" : "sh";
192
- const filePath = join(this.tempDir, `${id}.${extension}`);
193
- writeFileSync(filePath, command.endsWith("\n") ? command : `${command}\n`, "utf8");
194
-
195
- this.currentCommandId = id;
196
- this.state.running = true;
197
- this.transcript.startCommand(id, command, this.state.cwd);
198
- this.onStateChange();
199
- this.sendRaw(`__pi_eval ${quoteShellArg(id)} ${quoteShellArg(filePath)}\n`);
200
- }
201
-
202
- interrupt(): void {
203
- if (!this.process || !this.state.running) return;
204
- try {
205
- process.kill(-this.process.pid!, "SIGINT");
206
- } catch {
207
- // The shell may not own a process group anymore.
208
- this.process.kill("SIGINT");
209
- }
210
- }
211
-
212
- dispose(): void {
213
- this.disposed = true;
214
- this.readyPromise = null;
215
- this.readyResolve = null;
216
- this.readyReject = null;
217
- if (!this.process) return;
218
- try {
219
- process.kill(-this.process.pid!, "SIGKILL");
220
- } catch {
221
- // The shell may not own a process group anymore.
222
- this.process.kill("SIGKILL");
223
- }
224
- this.process = null;
225
- }
226
-
227
- private sendRaw(text: string): void {
228
- if (!this.process) return;
229
- this.process.stdin.write(text);
230
- }
231
-
232
- private handleChunk(chunk: string): void {
233
- const sanitized = stripAnsi(chunk).replace(/\r/g, "");
234
- if (!sanitized) return;
235
-
236
- this.buffer += sanitized;
237
- const parts = this.buffer.split("\n");
238
- this.buffer = parts.pop() ?? "";
239
-
240
- for (const rawLine of parts) {
241
- const line = rawLine.trimEnd();
242
- if (!this.state.ready) {
243
- if (line.startsWith(`${READY_SENTINEL}:`)) {
244
- this.state.ready = true;
245
- this.state.cwd = line.slice(READY_SENTINEL.length + 1) || this.state.cwd;
246
- this.readyResolve?.();
247
- this.readyResolve = null;
248
- this.readyReject = null;
249
- this.onStateChange();
250
- }
251
- continue;
252
- }
253
-
254
- if (line.startsWith(`${COMMAND_START_SENTINEL}:`)) {
255
- const [, id, cwd] = line.split(":");
256
- if (cwd) this.state.cwd = cwd;
257
- this.currentCommandId = id ?? this.currentCommandId;
258
- this.onStateChange();
259
- continue;
260
- }
261
-
262
- if (line.startsWith(`${COMMAND_DONE_SENTINEL}:`)) {
263
- const [, id, exitCodeText, cwd] = line.split(":");
264
- const exitCode = Number.parseInt(exitCodeText ?? "1", 10);
265
- this.state.running = false;
266
- this.state.lastExitCode = Number.isFinite(exitCode) ? exitCode : 1;
267
- if (cwd) this.state.cwd = cwd;
268
- if (id) {
269
- this.transcript.finishCommand(id, this.state.lastExitCode);
270
- const snapshot = this.transcript.getSnapshot();
271
- const command = snapshot.commands.find((entry) => entry.id === id);
272
- if (command && this.state.lastExitCode === 0) {
273
- this.onCommandSuccess(command.command, this.state.cwd);
274
- }
275
- }
276
- this.currentCommandId = null;
277
- this.onStateChange();
278
- continue;
279
- }
280
-
281
- if (!this.currentCommandId) continue;
282
- this.transcript.appendOutput(this.currentCommandId, line);
283
- this.onStateChange();
284
- }
285
- }
286
- }
@@ -1,108 +0,0 @@
1
- import type { BashCommandRecord, BashModeSettings, BashTranscriptSnapshot } from "./types.ts";
2
-
3
- function byteLength(value: string): number {
4
- return Buffer.byteLength(value, "utf8");
5
- }
6
-
7
- function compactLines(lines: string[]): string[] {
8
- const normalized: string[] = [];
9
- for (const line of lines) {
10
- const sanitized = line.replace(/\r/g, "");
11
- if (sanitized.length === 0) {
12
- normalized.push("");
13
- continue;
14
- }
15
- normalized.push(...sanitized.split("\n"));
16
- }
17
- return normalized;
18
- }
19
-
20
- export class BashTranscriptStore {
21
- private readonly settings: Pick<BashModeSettings, "transcriptMaxLines" | "transcriptMaxBytes">;
22
- private commands: BashCommandRecord[] = [];
23
- private commandIndex = new Map<string, BashCommandRecord>();
24
- private totalLines = 0;
25
- private totalBytes = 0;
26
- private truncatedCommands = 0;
27
-
28
- constructor(settings: Pick<BashModeSettings, "transcriptMaxLines" | "transcriptMaxBytes">) {
29
- this.settings = settings;
30
- }
31
-
32
- startCommand(id: string, command: string, cwdAtStart: string): BashCommandRecord {
33
- const entry: BashCommandRecord = {
34
- id,
35
- command,
36
- cwdAtStart,
37
- startedAt: Date.now(),
38
- output: [],
39
- outputBytes: 0,
40
- exitCode: null,
41
- finishedAt: null,
42
- truncated: false,
43
- };
44
-
45
- this.commands.push(entry);
46
- this.commandIndex.set(id, entry);
47
- this.enforceLimits();
48
- return entry;
49
- }
50
-
51
- appendOutput(id: string, chunk: string): void {
52
- const entry = this.commandIndex.get(id);
53
- if (!entry || !chunk) return;
54
-
55
- const lines = compactLines([chunk]);
56
- if (lines.length === 0) return;
57
-
58
- entry.output.push(...lines);
59
- const addedBytes = lines.reduce((sum, line) => sum + byteLength(line) + 1, 0);
60
- entry.outputBytes += addedBytes;
61
- this.totalLines += lines.length;
62
- this.totalBytes += addedBytes;
63
- this.enforceLimits();
64
- }
65
-
66
- finishCommand(id: string, exitCode: number): void {
67
- const entry = this.commandIndex.get(id);
68
- if (!entry) return;
69
-
70
- entry.exitCode = exitCode;
71
- entry.finishedAt = Date.now();
72
- }
73
-
74
- clear(): void {
75
- this.commands = [];
76
- this.commandIndex.clear();
77
- this.totalLines = 0;
78
- this.totalBytes = 0;
79
- this.truncatedCommands = 0;
80
- }
81
-
82
- getSnapshot(): BashTranscriptSnapshot {
83
- return {
84
- commands: this.commands.map((command) => ({
85
- ...command,
86
- output: [...command.output],
87
- })),
88
- totalLines: this.totalLines,
89
- totalBytes: this.totalBytes,
90
- truncatedCommands: this.truncatedCommands,
91
- };
92
- }
93
-
94
- private enforceLimits(): void {
95
- while (
96
- this.commands.length > 1
97
- && (this.totalLines > this.settings.transcriptMaxLines || this.totalBytes > this.settings.transcriptMaxBytes)
98
- ) {
99
- const removed = this.commands.shift();
100
- if (!removed) break;
101
- this.commandIndex.delete(removed.id);
102
- this.totalLines = Math.max(0, this.totalLines - removed.output.length);
103
- this.totalBytes = Math.max(0, this.totalBytes - removed.outputBytes);
104
- this.truncatedCommands += 1;
105
- removed.truncated = true;
106
- }
107
- }
108
- }
@@ -1,60 +0,0 @@
1
- export interface BashModeSettings {
2
- toggleShortcut: string | null;
3
- completions: boolean;
4
- transcriptMaxLines: number;
5
- transcriptMaxBytes: number;
6
- }
7
-
8
- export interface BashCommandRecord {
9
- id: string;
10
- command: string;
11
- startedAt: number;
12
- cwdAtStart: string;
13
- output: string[];
14
- outputBytes: number;
15
- exitCode: number | null;
16
- finishedAt: number | null;
17
- truncated: boolean;
18
- }
19
-
20
- export interface BashTranscriptSnapshot {
21
- commands: BashCommandRecord[];
22
- totalLines: number;
23
- totalBytes: number;
24
- truncatedCommands: number;
25
- }
26
-
27
- export interface GhostSuggestion {
28
- value: string;
29
- source:
30
- | "project-history"
31
- | "global-history"
32
- | "git"
33
- | "path"
34
- | "executable";
35
- }
36
-
37
- export interface ExtendedCompletionItem {
38
- value: string;
39
- label: string;
40
- description?: string;
41
- replacement: string;
42
- startCol: number;
43
- endCol: number;
44
- source:
45
- | "project-history"
46
- | "global-history"
47
- | "git"
48
- | "path"
49
- | "executable";
50
- score: number;
51
- }
52
-
53
- export interface ShellSessionState {
54
- ready: boolean;
55
- running: boolean;
56
- shellPath: string;
57
- shellName: string;
58
- cwd: string;
59
- lastExitCode: number | null;
60
- }