@selesai/code 0.9.14 → 0.10.0

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