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