@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,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
- }