@selesai/code 0.9.15 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +2 -2
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +2 -4
  6. package/dist/extensions/pi-zentui/.editorconfig +12 -0
  7. package/dist/extensions/pi-zentui/.github/CODEOWNERS +1 -0
  8. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/bug_report.yml +110 -0
  9. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config.yml +8 -0
  10. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config_question.yml +56 -0
  11. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
  12. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/rendering_issue.yml +106 -0
  13. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/runtime_detection.yml +103 -0
  14. package/dist/extensions/pi-zentui/.github/dependabot.yml +47 -0
  15. package/dist/extensions/pi-zentui/.github/pull_request_template.md +34 -0
  16. package/dist/extensions/pi-zentui/.github/release.yml +18 -0
  17. package/dist/extensions/pi-zentui/.github/workflows/ci.yml +79 -0
  18. package/dist/extensions/pi-zentui/.github/workflows/publish.yml +63 -0
  19. package/dist/extensions/pi-zentui/AGENTS.md +107 -0
  20. package/dist/extensions/pi-zentui/CONTRIBUTING.md +68 -0
  21. package/dist/extensions/pi-zentui/LICENSE +21 -0
  22. package/dist/extensions/pi-zentui/README.md +188 -0
  23. package/dist/extensions/pi-zentui/SECURITY.md +18 -0
  24. package/dist/extensions/pi-zentui/assets/main-cover.png +0 -0
  25. package/dist/extensions/pi-zentui/assets/screenshots/editor-accent-rail.png +0 -0
  26. package/dist/extensions/pi-zentui/assets/screenshots/editor-minimalist.png +0 -0
  27. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode-copy-friendly.png +0 -0
  28. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode.png +0 -0
  29. package/dist/extensions/pi-zentui/assets/screenshots/user-message-compact.png +0 -0
  30. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed-copy-friendly.png +0 -0
  31. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed.png +0 -0
  32. package/dist/extensions/pi-zentui/assets/screenshots/user-message-labeled.png +0 -0
  33. package/dist/extensions/pi-zentui/assets/zentui.png +0 -0
  34. package/dist/extensions/pi-zentui/biome.json +31 -0
  35. package/dist/extensions/pi-zentui/docs/configuration.md +509 -0
  36. package/dist/extensions/pi-zentui/docs/footer-format.md +162 -0
  37. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +99 -0
  38. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +457 -0
  39. package/dist/extensions/pi-zentui/extensions/zentui/completion-menu.ts +157 -0
  40. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +2100 -0
  41. package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +321 -0
  42. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +73 -0
  43. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +89 -0
  44. package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +364 -0
  45. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +122 -0
  46. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +812 -0
  47. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +568 -0
  48. package/dist/extensions/pi-zentui/extensions/zentui/git.ts +384 -0
  49. package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +252 -0
  50. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +1406 -0
  51. package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +1049 -0
  52. package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +75 -0
  53. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +463 -0
  54. package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +651 -0
  55. package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +120 -0
  56. package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +59 -0
  57. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +166 -0
  58. package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +98 -0
  59. package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +841 -0
  60. package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +93 -0
  61. package/dist/extensions/pi-zentui/extensions/zentui/session-lifecycle.ts +60 -0
  62. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1796 -0
  63. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +128 -0
  64. package/dist/extensions/pi-zentui/extensions/zentui/state.ts +94 -0
  65. package/dist/extensions/pi-zentui/extensions/zentui/style.ts +424 -0
  66. package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +82 -0
  67. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +1290 -0
  68. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +248 -0
  69. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +215 -0
  70. package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +206 -0
  71. package/dist/extensions/pi-zentui/extensions/zentui/working-line-messages.ts +24 -0
  72. package/dist/extensions/pi-zentui/extensions/zentui/working-line-spinners.ts +54 -0
  73. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +1433 -0
  74. package/dist/extensions/pi-zentui/package-lock.json +5311 -0
  75. package/dist/extensions/pi-zentui/package.json +60 -0
  76. package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +232 -0
  77. package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +669 -0
  78. package/dist/extensions/pi-zentui/test/completion-menu.test.ts +265 -0
  79. package/dist/extensions/pi-zentui/test/config.test.ts +3119 -0
  80. package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +405 -0
  81. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +260 -0
  82. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +1286 -0
  83. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +7196 -0
  84. package/dist/extensions/pi-zentui/test/extension-status.test.ts +182 -0
  85. package/dist/extensions/pi-zentui/test/fixtures/starship-nerd-font-symbols.toml +172 -0
  86. package/dist/extensions/pi-zentui/test/footer-format.test.ts +431 -0
  87. package/dist/extensions/pi-zentui/test/footer-layout.test.ts +113 -0
  88. package/dist/extensions/pi-zentui/test/footer-repository-path.test.ts +123 -0
  89. package/dist/extensions/pi-zentui/test/format.test.ts +865 -0
  90. package/dist/extensions/pi-zentui/test/git.test.ts +212 -0
  91. package/dist/extensions/pi-zentui/test/icons.test.ts +69 -0
  92. package/dist/extensions/pi-zentui/test/interaction-summary.test.ts +1402 -0
  93. package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +378 -0
  94. package/dist/extensions/pi-zentui/test/live-context.test.ts +139 -0
  95. package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +811 -0
  96. package/dist/extensions/pi-zentui/test/package-version.test.ts +321 -0
  97. package/dist/extensions/pi-zentui/test/project-refresh.test.ts +250 -0
  98. package/dist/extensions/pi-zentui/test/project-state.test.ts +159 -0
  99. package/dist/extensions/pi-zentui/test/repository-root.test.ts +108 -0
  100. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +825 -0
  101. package/dist/extensions/pi-zentui/test/runtime.test.ts +183 -0
  102. package/dist/extensions/pi-zentui/test/session-lifecycle.test.ts +40 -0
  103. package/dist/extensions/pi-zentui/test/settings-command.test.ts +1381 -0
  104. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +347 -0
  105. package/dist/extensions/pi-zentui/test/state.test.ts +84 -0
  106. package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +276 -0
  107. package/dist/extensions/pi-zentui/test/telemetry.test.ts +105 -0
  108. package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +327 -0
  109. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +164 -0
  110. package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +292 -0
  111. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +730 -0
  112. package/dist/extensions/pi-zentui/test/working-line-messages.test.ts +34 -0
  113. package/dist/extensions/pi-zentui/test/working-line.test.ts +1990 -0
  114. package/dist/extensions/pi-zentui/tsconfig.json +16 -0
  115. package/dist/extensions/tps.test.ts +38 -0
  116. package/dist/extensions/{pi-powerline-footer/tps.ts → tps.ts} +4 -0
  117. package/dist/skills/design-references/SKILL.md +31 -0
  118. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  119. package/dist/themes/adventure.json +84 -0
  120. package/dist/themes/adwaita-dark.json +84 -0
  121. package/dist/themes/arcoiris.json +84 -0
  122. package/dist/themes/arthur.json +84 -0
  123. package/dist/themes/atom.json +84 -0
  124. package/dist/themes/aura.json +84 -0
  125. package/dist/themes/black-metal-bathory.json +84 -0
  126. package/dist/themes/black-metal-burzum.json +84 -0
  127. package/dist/themes/black-metal-khold.json +84 -0
  128. package/dist/themes/box.json +84 -0
  129. package/dist/themes/brogrammer.json +84 -0
  130. package/dist/themes/carbonfox.json +84 -0
  131. package/dist/themes/catppuccin-mocha.json +84 -0
  132. package/dist/themes/citruszest.json +84 -0
  133. package/dist/themes/cursor-dark.json +84 -0
  134. package/dist/themes/cutie-pro.json +84 -0
  135. package/dist/themes/dark-modern.json +84 -0
  136. package/dist/themes/dark-pastel.json +84 -0
  137. package/dist/themes/dimmed-monokai.json +84 -0
  138. package/dist/themes/doom-peacock.json +84 -0
  139. package/dist/themes/dracula-plus.json +84 -0
  140. package/dist/themes/dracula.json +80 -0
  141. package/dist/themes/earthsong.json +84 -0
  142. package/dist/themes/everforest-dark-hard.json +84 -0
  143. package/dist/themes/everforest-dark.json +80 -0
  144. package/dist/themes/fahrenheit.json +84 -0
  145. package/dist/themes/flatland.json +84 -0
  146. package/dist/themes/flexoki-dark.json +84 -0
  147. package/dist/themes/front-end-delight.json +84 -0
  148. package/dist/themes/fun-forrest.json +84 -0
  149. package/dist/themes/galizur.json +84 -0
  150. package/dist/themes/github-dark-colorblind.json +84 -0
  151. package/dist/themes/github-dark-high-contrast.json +84 -0
  152. package/dist/themes/glacier.json +84 -0
  153. package/dist/themes/gruber-darker.json +84 -0
  154. package/dist/themes/gruvbox-dark-hard.json +84 -0
  155. package/dist/themes/gruvbox-dark.json +84 -0
  156. package/dist/themes/gruvbox-material.json +84 -0
  157. package/dist/themes/guezwhoz.json +84 -0
  158. package/dist/themes/hacktober.json +84 -0
  159. package/dist/themes/hardcore.json +84 -0
  160. package/dist/themes/havn-skumring.json +84 -0
  161. package/dist/themes/ic-orange-ppl.json +84 -0
  162. package/dist/themes/iterm2-smoooooth.json +84 -0
  163. package/dist/themes/iterm2-tango-dark.json +84 -0
  164. package/dist/themes/japanesque.json +84 -0
  165. package/dist/themes/jellybeans.json +84 -0
  166. package/dist/themes/kanagawa-wave.json +84 -0
  167. package/dist/themes/kurokula.json +84 -0
  168. package/dist/themes/later-this-evening.json +84 -0
  169. package/dist/themes/lovelace.json +84 -0
  170. package/dist/themes/material-darker.json +84 -0
  171. package/dist/themes/matte-black.json +84 -0
  172. package/dist/themes/mellow.json +84 -0
  173. package/dist/themes/miasma.json +84 -0
  174. package/dist/themes/nord.json +80 -0
  175. package/dist/themes/nvim-dark.json +84 -0
  176. package/dist/themes/one-dark.json +80 -0
  177. package/dist/themes/popping-and-locking.json +84 -0
  178. package/dist/themes/rose-pine.json +80 -0
  179. package/dist/themes/sea-shells.json +84 -0
  180. package/dist/themes/sleepy-hollow.json +84 -0
  181. package/dist/themes/smyck.json +84 -0
  182. package/dist/themes/solarized-dark.json +80 -0
  183. package/dist/themes/tokyo-night.json +80 -0
  184. package/dist/themes/tomorrow-night-bright.json +84 -0
  185. package/dist/themes/tomorrow-night-burns.json +84 -0
  186. package/dist/themes/tomorrow-night.json +84 -0
  187. package/dist/themes/twilight.json +84 -0
  188. package/dist/themes/vague.json +84 -0
  189. package/dist/themes/vesper.json +84 -0
  190. package/dist/themes/xcode-dark-hc.json +84 -0
  191. package/docs/custom-provider.md +0 -1
  192. package/package.json +3 -3
  193. package/dist/extensions/auto-model/auto-model.test.ts +0 -18
  194. package/dist/extensions/auto-model/classifier.ts +0 -23
  195. package/dist/extensions/auto-model/config.ts +0 -51
  196. package/dist/extensions/auto-model/index.ts +0 -22
  197. package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
  198. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  199. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  200. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  201. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  202. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  203. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  204. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  205. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  206. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  207. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  208. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  209. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  210. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  211. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  212. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  213. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  214. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  215. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  216. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  217. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  218. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  219. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  220. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  221. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  222. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  223. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  224. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  225. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  226. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  227. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  228. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  229. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  230. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  231. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  232. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  233. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  234. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  235. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  236. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  237. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  238. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  239. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  240. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  241. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  242. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  243. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  244. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  245. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  246. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  247. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  248. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  249. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  250. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  251. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  252. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  253. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  254. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  255. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  256. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  257. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  258. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  259. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  260. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  261. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  262. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  263. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  264. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  265. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  266. package/dist/extensions/preview-tools-disabled.ts +0 -11
  267. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -0,0 +1,384 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { promisify } from "node:util";
5
+
6
+ const execFileAsync = promisify(execFile);
7
+ const GIT_COMMAND_TIMEOUT_MS = 2_000;
8
+
9
+ export type GitOperationState =
10
+ | "REBASING"
11
+ | "MERGING"
12
+ | "CHERRY-PICKING"
13
+ | "REVERTING"
14
+ | "BISECTING";
15
+
16
+ /**
17
+ * Starship `git_commit`-style info derived from the existing porcelain probe.
18
+ *
19
+ * `oid` is the full `branch.oid` from `git status --porcelain=2 --branch`
20
+ * (free — no extra git call). `detached` mirrors `branch.head === "(detached)"`.
21
+ * `tag` is populated only when the caller opts into the exact-tag probe
22
+ * (`readGitStatus({ readExactTag: true })`); `null` means no exact-match tag
23
+ * or the probe was skipped.
24
+ */
25
+ export type GitCommitInfo = {
26
+ oid: string | null;
27
+ detached: boolean;
28
+ tag: string | null;
29
+ };
30
+
31
+ /**
32
+ * Starship `git_metrics`-style aggregate line-change counts.
33
+ * See https://starship.rs/config/#git-metrics
34
+ */
35
+ export type GitMetricsInfo = {
36
+ added: number;
37
+ deleted: number;
38
+ };
39
+
40
+ export type GitStatusSummary = {
41
+ branch?: string;
42
+ dirty: boolean;
43
+ ahead: number;
44
+ behind: number;
45
+ conflicted: number;
46
+ untracked: number;
47
+ stashed: number;
48
+ modified: number;
49
+ staged: number;
50
+ renamed: number;
51
+ deleted: number;
52
+ typechanged: number;
53
+ gitState?: GitOperationState;
54
+ gitStateLabel?: string;
55
+ commit?: GitCommitInfo;
56
+ metrics?: GitMetricsInfo | null;
57
+ };
58
+
59
+ export type GitReadResult =
60
+ | { kind: "ok"; status: GitStatusSummary }
61
+ | { kind: "not_a_repo" }
62
+ | { kind: "error" };
63
+
64
+ export type GitStatePaths = {
65
+ rebaseMerge?: string;
66
+ rebaseApply?: string;
67
+ mergeHead?: string;
68
+ cherryPickHead?: string;
69
+ revertHead?: string;
70
+ bisectLog?: string;
71
+ rebaseMsgnum?: string;
72
+ rebaseEnd?: string;
73
+ };
74
+
75
+ export function emptyGitStatus(): GitStatusSummary {
76
+ return {
77
+ branch: undefined,
78
+ dirty: false,
79
+ ahead: 0,
80
+ behind: 0,
81
+ conflicted: 0,
82
+ untracked: 0,
83
+ stashed: 0,
84
+ modified: 0,
85
+ staged: 0,
86
+ renamed: 0,
87
+ deleted: 0,
88
+ typechanged: 0,
89
+ gitState: undefined,
90
+ gitStateLabel: undefined,
91
+ commit: undefined,
92
+ metrics: undefined,
93
+ };
94
+ }
95
+
96
+ export function parseGitStatusPorcelain(stdoutText: string, stashCount: number): GitStatusSummary {
97
+ const status = emptyGitStatus();
98
+ status.stashed = stashCount;
99
+
100
+ let oid: string | null = null;
101
+ let sawBranchHead = false;
102
+ let detached = false;
103
+
104
+ for (const line of stdoutText.split(/\r?\n/)) {
105
+ if (!line) continue;
106
+ if (line.startsWith("# branch.oid ")) {
107
+ const value = line.slice("# branch.oid ".length).trim();
108
+ if (value && value !== "(initial)") oid = value;
109
+ continue;
110
+ }
111
+ if (line.startsWith("# branch.head ")) {
112
+ sawBranchHead = true;
113
+ const branch = line.slice("# branch.head ".length).trim();
114
+ if (branch === "(detached)") {
115
+ detached = true;
116
+ status.branch = undefined;
117
+ } else if (branch) {
118
+ status.branch = branch;
119
+ }
120
+ continue;
121
+ }
122
+ if (line.startsWith("# branch.ab ")) {
123
+ const match = line.match(/\+(\d+)\s+-(\d+)/);
124
+ if (match) {
125
+ status.ahead = Number(match[1] ?? 0);
126
+ status.behind = Number(match[2] ?? 0);
127
+ }
128
+ continue;
129
+ }
130
+ if (line.startsWith("#")) continue;
131
+
132
+ status.dirty = true;
133
+
134
+ if (line.startsWith("? ")) {
135
+ status.untracked += 1;
136
+ continue;
137
+ }
138
+ if (line.startsWith("u ")) {
139
+ status.conflicted += 1;
140
+ continue;
141
+ }
142
+ if (!(line.startsWith("1 ") || line.startsWith("2 "))) continue;
143
+
144
+ const xy = line.split(" ")[1] ?? "..";
145
+ const x = xy[0] ?? ".";
146
+ const y = xy[1] ?? ".";
147
+
148
+ if (x === "R") status.renamed += 1;
149
+ else if (x === "D") status.deleted += 1;
150
+ else if (x === "T") status.typechanged += 1;
151
+ else if (x !== "." && x !== " ") status.staged += 1;
152
+
153
+ if (y === "M") status.modified += 1;
154
+ else if (y === "D") status.deleted += 1;
155
+ else if (y === "T") status.typechanged += 1;
156
+ }
157
+
158
+ // Only populate commit info when we actually saw branch headers (inside
159
+ // a repo with commits). An unborn branch has no oid.
160
+ if (sawBranchHead) {
161
+ status.commit = { oid, detached, tag: null };
162
+ }
163
+
164
+ return status;
165
+ }
166
+
167
+ /**
168
+ * Parse `git diff --numstat` output into aggregate added/deleted counts.
169
+ *
170
+ * Each line is `added\tdeleted\tpath` (or `added\tdeleted\told\tnew` for
171
+ * renames). Binary rows carry `-` in both numeric columns and are skipped.
172
+ * Malformed/unparseable lines are ignored rather than aborting the sum.
173
+ *
174
+ * See https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---numstat
175
+ */
176
+ export function parseGitNumstat(stdoutText: string): GitMetricsInfo {
177
+ let added = 0;
178
+ let deleted = 0;
179
+ for (const line of stdoutText.split(/\r?\n/)) {
180
+ if (!line) continue;
181
+ const parts = line.split("\t");
182
+ // `added\tdeleted\tpath...` — need at least 3 tab-delimited fields.
183
+ if (parts.length < 3) continue;
184
+ const a = parts[0];
185
+ const d = parts[1];
186
+ // Binary files report `-` for both; skip.
187
+ if (a === "-" || d === "-") continue;
188
+ const na = Number(a);
189
+ const nd = Number(d);
190
+ if (!Number.isFinite(na) || !Number.isFinite(nd)) continue;
191
+ if (na < 0 || nd < 0) continue;
192
+ added += na;
193
+ deleted += nd;
194
+ }
195
+ return { added, deleted };
196
+ }
197
+
198
+ function readOptionalText(path: string | undefined): string | undefined {
199
+ if (!path || !existsSync(path)) return undefined;
200
+ try {
201
+ return readFileSync(path, "utf8").trim();
202
+ } catch {
203
+ return undefined;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Pure git operation-state detector. Paths that exist (truthy strings that
209
+ * callers verified with `existsSync`) select the active state in Starship order.
210
+ */
211
+ export function detectGitState(paths: GitStatePaths): {
212
+ gitState?: GitOperationState;
213
+ gitStateLabel?: string;
214
+ } {
215
+ if (paths.rebaseMerge || paths.rebaseApply) {
216
+ const msgnum = readOptionalText(paths.rebaseMsgnum);
217
+ const end = readOptionalText(paths.rebaseEnd);
218
+ if (msgnum && end) {
219
+ return { gitState: "REBASING", gitStateLabel: `REBASING ${msgnum}/${end}` };
220
+ }
221
+ return { gitState: "REBASING", gitStateLabel: "REBASING" };
222
+ }
223
+ if (paths.mergeHead) return { gitState: "MERGING", gitStateLabel: "MERGING" };
224
+ if (paths.cherryPickHead) {
225
+ return { gitState: "CHERRY-PICKING", gitStateLabel: "CHERRY-PICKING" };
226
+ }
227
+ if (paths.revertHead) return { gitState: "REVERTING", gitStateLabel: "REVERTING" };
228
+ if (paths.bisectLog) return { gitState: "BISECTING", gitStateLabel: "BISECTING" };
229
+ return {};
230
+ }
231
+
232
+ async function resolveGitPath(cwd: string, pathSpec: string): Promise<string | undefined> {
233
+ try {
234
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--git-path", pathSpec], {
235
+ cwd,
236
+ timeout: GIT_COMMAND_TIMEOUT_MS,
237
+ });
238
+ const resolved = (typeof stdout === "string" ? stdout : String(stdout)).trim();
239
+ if (!resolved) return undefined;
240
+ return resolved.startsWith("/") ? resolved : join(cwd, resolved);
241
+ } catch {
242
+ return undefined;
243
+ }
244
+ }
245
+
246
+ async function readGitOperationState(cwd: string): Promise<{
247
+ gitState?: GitOperationState;
248
+ gitStateLabel?: string;
249
+ }> {
250
+ const [rebaseMerge, rebaseApply, mergeHead, cherryPickHead, revertHead, bisectLog] =
251
+ await Promise.all([
252
+ resolveGitPath(cwd, "rebase-merge"),
253
+ resolveGitPath(cwd, "rebase-apply"),
254
+ resolveGitPath(cwd, "MERGE_HEAD"),
255
+ resolveGitPath(cwd, "CHERRY_PICK_HEAD"),
256
+ resolveGitPath(cwd, "REVERT_HEAD"),
257
+ resolveGitPath(cwd, "BISECT_LOG"),
258
+ ]);
259
+
260
+ const existing = (path: string | undefined) => (path && existsSync(path) ? path : undefined);
261
+ const rebaseDir = existing(rebaseMerge) ?? existing(rebaseApply);
262
+
263
+ return detectGitState({
264
+ rebaseMerge: existing(rebaseMerge),
265
+ rebaseApply: existing(rebaseApply),
266
+ mergeHead: existing(mergeHead),
267
+ cherryPickHead: existing(cherryPickHead),
268
+ revertHead: existing(revertHead),
269
+ bisectLog: existing(bisectLog),
270
+ rebaseMsgnum: rebaseDir ? join(rebaseDir, "msgnum") : undefined,
271
+ rebaseEnd: rebaseDir ? join(rebaseDir, "end") : undefined,
272
+ });
273
+ }
274
+
275
+ function isNotARepoError(error: unknown): boolean {
276
+ const message =
277
+ error instanceof Error
278
+ ? `${error.message}\n${"stderr" in error ? String((error as { stderr?: unknown }).stderr ?? "") : ""}`
279
+ : String(error);
280
+ return /not a git repository|outside repository|not a git repo/i.test(message);
281
+ }
282
+
283
+ /**
284
+ * Options for the optional probes that piggyback on the existing git refresh.
285
+ * Both default to `false` so no extra git process is spawned unless a segment
286
+ * is enabled and needs the data.
287
+ */
288
+ export type ReadGitStatusOptions = {
289
+ /** Run `git describe --tags --exact-match HEAD` for the git_commit segment. */
290
+ readExactTag?: boolean;
291
+ /**
292
+ * Run `git diff HEAD --numstat` for the git_metrics segment. Uses the
293
+ * Starship-like “total dirty” view (staged + unstaged combined).
294
+ */
295
+ readMetrics?: boolean;
296
+ /** Add `--ignore-submodules=all` to the metrics diff when requested. */
297
+ ignoreSubmodules?: boolean;
298
+ };
299
+
300
+ export async function readGitStatus(
301
+ cwd: string,
302
+ options: ReadGitStatusOptions = {},
303
+ ): Promise<GitReadResult> {
304
+ const readExactTag = options.readExactTag === true;
305
+ const readMetrics = options.readMetrics === true;
306
+ try {
307
+ const numstatArgs = ["diff", "HEAD", "--numstat"];
308
+ if (options.ignoreSubmodules) numstatArgs.push("--ignore-submodules=all");
309
+ const [{ stdout: statusStdout }, stashResult, tagResult, metricsResult] = await Promise.all([
310
+ execFileAsync("git", ["status", "--porcelain=2", "--branch"], {
311
+ cwd,
312
+ timeout: GIT_COMMAND_TIMEOUT_MS,
313
+ }),
314
+ execFileAsync("git", ["stash", "list"], {
315
+ cwd,
316
+ timeout: GIT_COMMAND_TIMEOUT_MS,
317
+ }).catch(() => ({ stdout: "" })),
318
+ readExactTag
319
+ ? execFileAsync("git", ["describe", "--tags", "--exact-match", "HEAD"], {
320
+ cwd,
321
+ timeout: GIT_COMMAND_TIMEOUT_MS,
322
+ }).then(
323
+ (r) => ({ stdout: typeof r.stdout === "string" ? r.stdout : String(r.stdout) }),
324
+ () => ({ stdout: "" }),
325
+ )
326
+ : Promise.resolve({ stdout: "" }),
327
+ readMetrics
328
+ ? execFileAsync("git", numstatArgs, {
329
+ cwd,
330
+ timeout: GIT_COMMAND_TIMEOUT_MS,
331
+ }).then(
332
+ (r) => ({ stdout: typeof r.stdout === "string" ? r.stdout : String(r.stdout) }),
333
+ () => ({ stdout: "", failed: true as const }),
334
+ )
335
+ : Promise.resolve({ stdout: "", failed: true as const }),
336
+ ]);
337
+ const stdoutText = typeof statusStdout === "string" ? statusStdout : String(statusStdout);
338
+ const stashStdout =
339
+ typeof stashResult.stdout === "string" ? stashResult.stdout : String(stashResult.stdout);
340
+ const stashCount = stashStdout.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
341
+ const status = parseGitStatusPorcelain(stdoutText, stashCount);
342
+ if (status.commit) {
343
+ const tagStdout =
344
+ typeof tagResult.stdout === "string" ? tagResult.stdout : String(tagResult.stdout);
345
+ const tag = tagStdout.trim();
346
+ status.commit = { ...status.commit, tag: tag || null };
347
+ }
348
+ if (readMetrics) {
349
+ if ("failed" in metricsResult && metricsResult.failed) {
350
+ status.metrics = null;
351
+ } else {
352
+ const metricsStdout =
353
+ typeof metricsResult.stdout === "string"
354
+ ? metricsResult.stdout
355
+ : String(metricsResult.stdout);
356
+ status.metrics = parseGitNumstat(metricsStdout);
357
+ }
358
+ }
359
+ const operation = await readGitOperationState(cwd);
360
+ return {
361
+ kind: "ok",
362
+ status: {
363
+ ...status,
364
+ ...operation,
365
+ },
366
+ };
367
+ } catch (error) {
368
+ if (isNotARepoError(error)) return { kind: "not_a_repo" };
369
+
370
+ // Distinguish not-a-repo vs transient with a cheap rev-parse on the error path.
371
+ try {
372
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
373
+ cwd,
374
+ timeout: GIT_COMMAND_TIMEOUT_MS,
375
+ });
376
+ const inside = (typeof stdout === "string" ? stdout : String(stdout)).trim();
377
+ if (inside !== "true") return { kind: "not_a_repo" };
378
+ return { kind: "error" };
379
+ } catch (inner) {
380
+ if (isNotARepoError(inner)) return { kind: "not_a_repo" };
381
+ return { kind: "error" };
382
+ }
383
+ }
384
+ }
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Icon mode defaults and resolvers.
3
+ *
4
+ * Nerd defaults must stay byte-identical to historical `defaultConfig.icons`,
5
+ * except where intentionally changed to match the Starship Nerd Font preset.
6
+ * User string overrides always win over mode defaults.
7
+ */
8
+
9
+ export type IconMode = "auto" | "nerd" | "ascii";
10
+
11
+ export type IconGlyphs = {
12
+ cwd: string;
13
+ git: string;
14
+ ahead: string;
15
+ behind: string;
16
+ diverged: string;
17
+ conflicted: string;
18
+ untracked: string;
19
+ stashed: string;
20
+ modified: string;
21
+ staged: string;
22
+ renamed: string;
23
+ deleted: string;
24
+ typechanged: string;
25
+ cacheHit: string;
26
+ editorPrompt: string;
27
+ rail: string;
28
+ username: string;
29
+ time: string;
30
+ os: string;
31
+ package: string;
32
+ };
33
+
34
+ export type ResolvedIcons = IconGlyphs & { mode: IconMode };
35
+
36
+ export const ICON_GLYPH_KEYS = [
37
+ "cwd",
38
+ "git",
39
+ "ahead",
40
+ "behind",
41
+ "diverged",
42
+ "conflicted",
43
+ "untracked",
44
+ "stashed",
45
+ "modified",
46
+ "staged",
47
+ "renamed",
48
+ "deleted",
49
+ "typechanged",
50
+ "cacheHit",
51
+ "editorPrompt",
52
+ "rail",
53
+ "username",
54
+ "time",
55
+ "os",
56
+ "package",
57
+ ] as const satisfies readonly (keyof IconGlyphs)[];
58
+
59
+ /**
60
+ * Nerd Font defaults.
61
+ *
62
+ * The `cwd` icon is intentionally empty — Starship's `directory` module
63
+ * has no default symbol. Other defaults match historical values; new
64
+ * additions (e.g. `package`) come from the Starship Nerd Font preset
65
+ * (https://starship.rs/presets/nerd-font).
66
+ */
67
+ export const NERD_DEFAULT_ICONS: IconGlyphs = {
68
+ cwd: "",
69
+ git: "",
70
+ ahead: "↑",
71
+ behind: "↓",
72
+ diverged: "⇕",
73
+ conflicted: "=",
74
+ untracked: "?",
75
+ stashed: "$",
76
+ modified: "!",
77
+ staged: "+",
78
+ renamed: "»",
79
+ deleted: "✘",
80
+ typechanged: "T",
81
+ cacheHit: "󰆼",
82
+ editorPrompt: "",
83
+ rail: "│",
84
+ username: "",
85
+ time: "",
86
+ os: "",
87
+ // Starship Nerd Font preset — `package` module glyph.
88
+ package: "",
89
+ };
90
+
91
+ export const ASCII_DEFAULT_ICONS: IconGlyphs = {
92
+ cwd: "",
93
+ git: "*",
94
+ ahead: "^",
95
+ behind: "v",
96
+ diverged: "^v",
97
+ conflicted: "=",
98
+ untracked: "?",
99
+ stashed: "$",
100
+ modified: "!",
101
+ staged: "+",
102
+ renamed: ">",
103
+ deleted: "x",
104
+ typechanged: "T",
105
+ cacheHit: "c",
106
+ editorPrompt: "",
107
+ rail: "|",
108
+ username: "@",
109
+ time: "t",
110
+ os: "o",
111
+ package: "pkg",
112
+ };
113
+
114
+ export const OS_PLATFORM_ICONS_NERD: Record<string, string> = {
115
+ darwin: "\uf179",
116
+ linux: "\uf17c",
117
+ win32: "\uf17a",
118
+ };
119
+
120
+ export const OS_PLATFORM_ICONS_ASCII: Record<string, string> = {
121
+ darwin: "mac",
122
+ linux: "linux",
123
+ win32: "win",
124
+ };
125
+
126
+ /** Short ASCII labels keyed by runtime `name`. */
127
+ export const RUNTIME_ASCII_SYMBOLS: Record<string, string> = {
128
+ xmake: "xm",
129
+ maven: "mvn",
130
+ gradle: "grd",
131
+ bun: "bun",
132
+ deno: "deno",
133
+ lua: "lua",
134
+ nodejs: "node",
135
+ python: "py",
136
+ golang: "go",
137
+ rust: "rs",
138
+ java: "java",
139
+ ruby: "rb",
140
+ php: "php",
141
+ buf: "buf",
142
+ cmake: "cmake",
143
+ cpp: "c++",
144
+ c: "c",
145
+ cobol: "cob",
146
+ conda: "conda",
147
+ crystal: "cr",
148
+ dart: "dart",
149
+ dotnet: ".net",
150
+ elixir: "ex",
151
+ elm: "elm",
152
+ erlang: "erl",
153
+ fennel: "fnl",
154
+ fortran: "f90",
155
+ gleam: "glm",
156
+ guix_shell: "guix",
157
+ haskell: "hs",
158
+ haxe: "hx",
159
+ helm: "helm",
160
+ julia: "jl",
161
+ kotlin: "kt",
162
+ meson: "meson",
163
+ mojo: "mojo",
164
+ nim: "nim",
165
+ nix_shell: "nix",
166
+ ocaml: "ml",
167
+ odin: "odin",
168
+ opa: "opa",
169
+ perl: "pl",
170
+ pixi: "pixi",
171
+ pulumi: "pul",
172
+ purescript: "purs",
173
+ raku: "raku",
174
+ red: "red",
175
+ rlang: "R",
176
+ scala: "scala",
177
+ solidity: "sol",
178
+ spack: "spack",
179
+ swift: "swift",
180
+ terraform: "tf",
181
+ typst: "typ",
182
+ vagrant: "vag",
183
+ vlang: "v",
184
+ zig: "zig",
185
+ };
186
+
187
+ export function isIconMode(value: unknown): value is IconMode {
188
+ return value === "auto" || value === "nerd" || value === "ascii";
189
+ }
190
+
191
+ export function normalizeIconMode(value: unknown): IconMode {
192
+ return isIconMode(value) ? value : "auto";
193
+ }
194
+
195
+ export function modeDefaultIcons(mode: IconMode): IconGlyphs {
196
+ return mode === "ascii" ? { ...ASCII_DEFAULT_ICONS } : { ...NERD_DEFAULT_ICONS };
197
+ }
198
+
199
+ export function resolveConfiguredIcons(
200
+ mode: IconMode,
201
+ overrides: Partial<IconGlyphs> = {},
202
+ ): ResolvedIcons {
203
+ const base = modeDefaultIcons(mode);
204
+ const rail =
205
+ typeof overrides.rail === "string" && overrides.rail.trim().length > 0
206
+ ? overrides.rail
207
+ : base.rail;
208
+ return {
209
+ mode,
210
+ ...base,
211
+ ...overrides,
212
+ rail,
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Honor a custom `icons.os` when it differs from the mode default.
218
+ * Otherwise map by platform for the active mode.
219
+ */
220
+ export function resolveOsIcon(
221
+ configuredOsIcon: string,
222
+ mode: IconMode = "auto",
223
+ platform: string = process.platform,
224
+ ): string {
225
+ const modeDefault = modeDefaultIcons(mode).os;
226
+ if (configuredOsIcon !== modeDefault) return configuredOsIcon;
227
+ const platformMap = mode === "ascii" ? OS_PLATFORM_ICONS_ASCII : OS_PLATFORM_ICONS_NERD;
228
+ return platformMap[platform] ?? configuredOsIcon;
229
+ }
230
+
231
+ export function resolveRuntimeSymbol(
232
+ name: string,
233
+ nerdSymbol: string,
234
+ mode: IconMode = "auto",
235
+ ): string {
236
+ if (mode !== "ascii") return nerdSymbol;
237
+ return RUNTIME_ASCII_SYMBOLS[name] ?? (name.slice(0, 3) || "*");
238
+ }
239
+
240
+ /**
241
+ * Resolve the package-version segment icon for the active mode.
242
+ *
243
+ * Honors a configured `icons.package` override; otherwise falls back to the
244
+ * mode default (Nerd Font preset / ASCII label).
245
+ */
246
+ export function resolvePackageIcon(configuredPackageIcon: string, mode: IconMode = "auto"): string {
247
+ const modeDefault = modeDefaultIcons(mode).package;
248
+ if (typeof configuredPackageIcon === "string" && configuredPackageIcon.length > 0) {
249
+ return configuredPackageIcon;
250
+ }
251
+ return modeDefault;
252
+ }