@zakmandhro/bunti 0.1.4 → 0.2.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 (203) hide show
  1. package/AGENTS.md +65 -0
  2. package/README.md +145 -34
  3. package/dist/cli.d.ts +44 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +147 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/colors.d.ts +144 -0
  8. package/dist/colors.d.ts.map +1 -0
  9. package/dist/colors.js +426 -0
  10. package/dist/colors.js.map +1 -0
  11. package/dist/components/Box.d.ts +11 -0
  12. package/dist/components/Box.d.ts.map +1 -0
  13. package/dist/components/Box.js +9 -0
  14. package/dist/components/Box.js.map +1 -0
  15. package/dist/components/Button.d.ts +24 -0
  16. package/dist/components/Button.d.ts.map +1 -0
  17. package/dist/components/Button.js +102 -0
  18. package/dist/components/Button.js.map +1 -0
  19. package/dist/components/Card.d.ts +33 -0
  20. package/dist/components/Card.d.ts.map +1 -0
  21. package/dist/components/Card.js +79 -0
  22. package/dist/components/Card.js.map +1 -0
  23. package/dist/components/Header.d.ts +23 -0
  24. package/dist/components/Header.d.ts.map +1 -0
  25. package/dist/components/Header.js +58 -0
  26. package/dist/components/Header.js.map +1 -0
  27. package/dist/components/Input.d.ts +25 -0
  28. package/dist/components/Input.d.ts.map +1 -0
  29. package/dist/components/Input.js +232 -0
  30. package/dist/components/Input.js.map +1 -0
  31. package/dist/components/Link.d.ts +21 -0
  32. package/dist/components/Link.d.ts.map +1 -0
  33. package/dist/components/Link.js +29 -0
  34. package/dist/components/Link.js.map +1 -0
  35. package/dist/components/Modal.d.ts +38 -0
  36. package/dist/components/Modal.d.ts.map +1 -0
  37. package/dist/components/Modal.js +87 -0
  38. package/dist/components/Modal.js.map +1 -0
  39. package/dist/components/Progress.d.ts +27 -0
  40. package/dist/components/Progress.d.ts.map +1 -0
  41. package/dist/components/Progress.js +39 -0
  42. package/dist/components/Progress.js.map +1 -0
  43. package/dist/components/Spinner.d.ts +19 -0
  44. package/dist/components/Spinner.d.ts.map +1 -0
  45. package/dist/components/Spinner.js +27 -0
  46. package/dist/components/Spinner.js.map +1 -0
  47. package/dist/components/index.d.ts +10 -0
  48. package/dist/components/index.d.ts.map +1 -0
  49. package/dist/components/index.js +10 -0
  50. package/dist/components/index.js.map +1 -0
  51. package/dist/data/nf-glyphs.d.ts +11 -0
  52. package/dist/data/nf-glyphs.d.ts.map +1 -0
  53. package/dist/data/nf-glyphs.js +535 -0
  54. package/dist/data/nf-glyphs.js.map +1 -0
  55. package/dist/data/nf-names.d.ts +8 -0
  56. package/dist/data/nf-names.d.ts.map +1 -0
  57. package/dist/data/nf-names.js +2 -0
  58. package/dist/data/nf-names.js.map +1 -0
  59. package/dist/demo-registry.d.ts +27 -0
  60. package/dist/demo-registry.d.ts.map +1 -0
  61. package/dist/demo-registry.js +57 -0
  62. package/dist/demo-registry.js.map +1 -0
  63. package/dist/demos/2048.ts +743 -0
  64. package/dist/demos/animation.ts +246 -0
  65. package/dist/demos/demo-layout.ts +124 -0
  66. package/dist/demos/engine.ts +374 -0
  67. package/dist/demos/interaction.ts +91 -0
  68. package/dist/demos/login.ts +203 -0
  69. package/dist/demos/mission-control.ts +558 -0
  70. package/dist/demos/showcase.ts +224 -0
  71. package/dist/detect.d.ts +104 -0
  72. package/dist/detect.d.ts.map +1 -0
  73. package/dist/detect.js +227 -0
  74. package/dist/detect.js.map +1 -0
  75. package/dist/diagnostics.d.ts +73 -0
  76. package/dist/diagnostics.d.ts.map +1 -0
  77. package/dist/diagnostics.js +225 -0
  78. package/dist/diagnostics.js.map +1 -0
  79. package/dist/dsl/context.d.ts +11 -0
  80. package/dist/dsl/context.d.ts.map +1 -0
  81. package/dist/dsl/context.js +563 -0
  82. package/dist/dsl/context.js.map +1 -0
  83. package/dist/dsl/hooks.d.ts +12 -0
  84. package/dist/dsl/hooks.d.ts.map +1 -0
  85. package/dist/dsl/hooks.js +148 -0
  86. package/dist/dsl/hooks.js.map +1 -0
  87. package/dist/dsl/interaction.d.ts +16 -0
  88. package/dist/dsl/interaction.d.ts.map +1 -0
  89. package/dist/dsl/interaction.js +101 -0
  90. package/dist/dsl/interaction.js.map +1 -0
  91. package/dist/dsl/layers.d.ts +14 -0
  92. package/dist/dsl/layers.d.ts.map +1 -0
  93. package/dist/dsl/layers.js +87 -0
  94. package/dist/dsl/layers.js.map +1 -0
  95. package/dist/dsl/motion.d.ts +10 -0
  96. package/dist/dsl/motion.d.ts.map +1 -0
  97. package/dist/dsl/motion.js +150 -0
  98. package/dist/dsl/motion.js.map +1 -0
  99. package/dist/dsl/render.d.ts +22 -0
  100. package/dist/dsl/render.d.ts.map +1 -0
  101. package/dist/dsl/render.js +69 -0
  102. package/dist/dsl/render.js.map +1 -0
  103. package/dist/dsl/types.d.ts +510 -0
  104. package/dist/dsl/types.d.ts.map +1 -0
  105. package/dist/dsl/types.js +33 -0
  106. package/dist/dsl/types.js.map +1 -0
  107. package/dist/dsl.d.ts +18 -0
  108. package/dist/dsl.d.ts.map +1 -0
  109. package/dist/dsl.js +17 -0
  110. package/dist/dsl.js.map +1 -0
  111. package/dist/easing.d.ts +45 -0
  112. package/dist/easing.d.ts.map +1 -0
  113. package/dist/easing.js +121 -0
  114. package/dist/easing.js.map +1 -0
  115. package/dist/geometry.d.ts +77 -0
  116. package/dist/geometry.d.ts.map +1 -0
  117. package/dist/geometry.js +130 -0
  118. package/dist/geometry.js.map +1 -0
  119. package/dist/icons-full.d.ts +5 -0
  120. package/dist/icons-full.d.ts.map +1 -0
  121. package/dist/icons-full.js +31 -0
  122. package/dist/icons-full.js.map +1 -0
  123. package/dist/icons.d.ts +442 -0
  124. package/dist/icons.d.ts.map +1 -0
  125. package/dist/icons.js +314 -0
  126. package/dist/icons.js.map +1 -0
  127. package/dist/index.d.ts +228 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +46 -0
  130. package/dist/index.js.map +1 -0
  131. package/dist/input.d.ts +177 -0
  132. package/dist/input.d.ts.map +1 -0
  133. package/dist/input.js +533 -0
  134. package/dist/input.js.map +1 -0
  135. package/dist/layout.d.ts +194 -0
  136. package/dist/layout.d.ts.map +1 -0
  137. package/dist/layout.js +673 -0
  138. package/dist/layout.js.map +1 -0
  139. package/dist/render.d.ts +49 -0
  140. package/dist/render.d.ts.map +1 -0
  141. package/dist/render.js +650 -0
  142. package/dist/render.js.map +1 -0
  143. package/dist/state.d.ts +226 -0
  144. package/dist/state.d.ts.map +1 -0
  145. package/dist/state.js +130 -0
  146. package/dist/state.js.map +1 -0
  147. package/dist/theme.d.ts +129 -0
  148. package/dist/theme.d.ts.map +1 -0
  149. package/dist/theme.js +167 -0
  150. package/dist/theme.js.map +1 -0
  151. package/dist/themes/catppuccin-mocha.d.ts +19 -0
  152. package/dist/themes/catppuccin-mocha.d.ts.map +1 -0
  153. package/dist/themes/catppuccin-mocha.js +36 -0
  154. package/dist/themes/catppuccin-mocha.js.map +1 -0
  155. package/dist/themes/dracula.d.ts +21 -0
  156. package/dist/themes/dracula.d.ts.map +1 -0
  157. package/dist/themes/dracula.js +37 -0
  158. package/dist/themes/dracula.js.map +1 -0
  159. package/dist/themes/github-light.d.ts +21 -0
  160. package/dist/themes/github-light.d.ts.map +1 -0
  161. package/dist/themes/github-light.js +36 -0
  162. package/dist/themes/github-light.js.map +1 -0
  163. package/dist/themes/index.d.ts +19 -0
  164. package/dist/themes/index.d.ts.map +1 -0
  165. package/dist/themes/index.js +17 -0
  166. package/dist/themes/index.js.map +1 -0
  167. package/dist/themes/nord.d.ts +23 -0
  168. package/dist/themes/nord.d.ts.map +1 -0
  169. package/dist/themes/nord.js +38 -0
  170. package/dist/themes/nord.js.map +1 -0
  171. package/dist/themes/one-dark-pro.d.ts +21 -0
  172. package/dist/themes/one-dark-pro.d.ts.map +1 -0
  173. package/dist/themes/one-dark-pro.js +36 -0
  174. package/dist/themes/one-dark-pro.js.map +1 -0
  175. package/dist/themes/tokyo-night.d.ts +20 -0
  176. package/dist/themes/tokyo-night.d.ts.map +1 -0
  177. package/dist/themes/tokyo-night.js +37 -0
  178. package/dist/themes/tokyo-night.js.map +1 -0
  179. package/dist/utils.d.ts +28 -0
  180. package/dist/utils.d.ts.map +1 -0
  181. package/dist/utils.js +158 -0
  182. package/dist/utils.js.map +1 -0
  183. package/dist/vendor/colors.d.ts +45 -0
  184. package/dist/vendor/colors.d.ts.map +1 -0
  185. package/dist/vendor/colors.js +72 -0
  186. package/dist/vendor/colors.js.map +1 -0
  187. package/llms.txt +79 -0
  188. package/package.json +52 -11
  189. package/src/colors.ts +0 -255
  190. package/src/components/Button.ts +0 -104
  191. package/src/components/Card.ts +0 -53
  192. package/src/components/Header.ts +0 -65
  193. package/src/components/Input.ts +0 -124
  194. package/src/components/index.ts +0 -4
  195. package/src/data/glyphs.ts +0 -30
  196. package/src/detect.ts +0 -60
  197. package/src/dsl.ts +0 -661
  198. package/src/icons.ts +0 -186
  199. package/src/index.ts +0 -72
  200. package/src/layout.ts +0 -640
  201. package/src/render.ts +0 -302
  202. package/src/state.ts +0 -148
  203. package/src/utils.ts +0 -165
@@ -0,0 +1,224 @@
1
+ import { Box } from '../components/index.js';
2
+ import {
3
+ ICON_MAP,
4
+ joinHorizontal,
5
+ box as renderBox,
6
+ splitRect,
7
+ truncate,
8
+ } from '../index.js';
9
+ import { demo } from './demo-layout';
10
+
11
+ const PALETTE = [
12
+ 'bunti-blue',
13
+ 'sky',
14
+ 'plasma',
15
+ 'rose',
16
+ 'gold',
17
+ 'mint',
18
+ 'success',
19
+ 'warning',
20
+ 'error',
21
+ ];
22
+
23
+ const BORDERS = ['default', 'rounded', 'double', 'dashed', 'frame'] as const;
24
+
25
+ demo('SHOWCASE', (ctx, bounds) => {
26
+ const { color, gradient, icon } = ctx;
27
+ const width = Math.min(bounds.w - 4, 104);
28
+ const shell = bounds.place({
29
+ y: 0,
30
+ width,
31
+ height: Math.min(bounds.h, 27),
32
+ });
33
+ const [left, right] = splitRect(shell, {
34
+ direction: 'horizontal',
35
+ constraints: ['44%', '1fr'],
36
+ gap: 2,
37
+ });
38
+ const [palette, layout] = splitRect(left!, {
39
+ direction: 'vertical',
40
+ constraints: [11, '1fr'],
41
+ gap: 1,
42
+ });
43
+ const [frames, textLab, iconLab] = splitRect(right!, {
44
+ direction: 'vertical',
45
+ constraints: [8, 9, '1fr'],
46
+ gap: 1,
47
+ });
48
+
49
+ Box(
50
+ ctx,
51
+ {
52
+ x: palette?.x,
53
+ y: palette?.y,
54
+ width: palette?.width,
55
+ height: palette?.height,
56
+ border: 'rounded',
57
+ borderColor: 'bunti-blue',
58
+ padding: [1, 2],
59
+ },
60
+ ({ text }) => {
61
+ text(color.fg('bunti-blue', color.bold('COLOR SYSTEM\n')));
62
+ for (const [index, name] of PALETTE.entries()) {
63
+ if (index > 0 && index % 3 === 0) text('\n');
64
+ text(color.dim(name.padEnd(12)));
65
+ text(color.fg(name, '████'));
66
+ text(' ');
67
+ }
68
+ },
69
+ );
70
+
71
+ Box(
72
+ ctx,
73
+ {
74
+ x: layout?.x,
75
+ y: layout?.y,
76
+ width: layout?.width,
77
+ height: layout?.height,
78
+ border: 'rounded',
79
+ borderColor: 'mint',
80
+ padding: [1, 2],
81
+ },
82
+ ({ text }) => {
83
+ text(color.fg('mint', color.bold('RECT LAYOUT\n\n')));
84
+ text(color.dim('splitRect: fixed + percent + fill tracks\n\n'));
85
+
86
+ const tracks = splitRect(
87
+ { x: 0, y: 0, width: Math.max(1, (layout?.width ?? 8) - 6), height: 3 },
88
+ {
89
+ direction: 'horizontal',
90
+ constraints: [8, '35%', '1fr'],
91
+ gap: 1,
92
+ },
93
+ );
94
+
95
+ const blocks = tracks.map((track, index) =>
96
+ renderBox(
97
+ color.fg(
98
+ 'white',
99
+ index === 0 ? '8 cols' : index === 1 ? '35%' : 'fill',
100
+ ),
101
+ {
102
+ width: track.width,
103
+ height: track.height,
104
+ border: 'none',
105
+ bgColor: index === 0 ? 'ocean' : index === 1 ? 'nebula' : 'plasma',
106
+ align: 'center',
107
+ valign: 'middle',
108
+ },
109
+ ),
110
+ );
111
+ text(joinHorizontal(...blocks));
112
+ text('\n\n');
113
+ text(color.dim('Content stays inside the assigned Rect.'));
114
+ },
115
+ );
116
+
117
+ Box(
118
+ ctx,
119
+ {
120
+ x: frames?.x,
121
+ y: frames?.y,
122
+ width: frames?.width,
123
+ height: frames?.height,
124
+ border: 'rounded',
125
+ borderColor: 'plasma',
126
+ padding: [1, 2],
127
+ },
128
+ ({ text }) => {
129
+ text(color.fg('plasma', color.bold('BORDERS + DEPTH\n\n')));
130
+ const cells = splitRect(
131
+ { x: 0, y: 3, width: Math.max(1, (frames?.width ?? 8) - 6), height: 3 },
132
+ {
133
+ direction: 'horizontal',
134
+ constraints: BORDERS.map(() => '1fr'),
135
+ gap: 1,
136
+ },
137
+ );
138
+
139
+ const blocks = BORDERS.map((border, index) => {
140
+ const cell = cells[index]!;
141
+ return renderBox(
142
+ color.fg('silver', truncate(border, Math.max(1, cell.width - 2))),
143
+ {
144
+ width: cell.width,
145
+ height: cell.height,
146
+ border,
147
+ borderColor: index % 2 === 0 ? 'silver' : 'gold',
148
+ align: 'center',
149
+ valign: 'middle',
150
+ },
151
+ );
152
+ });
153
+ text(joinHorizontal(...blocks));
154
+ },
155
+ );
156
+
157
+ Box(
158
+ ctx,
159
+ {
160
+ x: textLab?.x,
161
+ y: textLab?.y,
162
+ width: textLab?.width,
163
+ height: textLab?.height,
164
+ border: 'rounded',
165
+ borderColor: 'gold',
166
+ padding: [1, 2],
167
+ },
168
+ ({ text, box }) => {
169
+ text(color.fg('gold', color.bold('TEXT + GRADIENTS\n\n')));
170
+ const bar = gradient({
171
+ colors: ['sky', 'plasma', 'rose', 'gold', 'mint'],
172
+ direction: 'horizontal',
173
+ steps: Math.max(1, (textLab?.width ?? 8) - 6),
174
+ });
175
+ box(
176
+ {
177
+ width: '100%',
178
+ height: 1,
179
+ border: 'none',
180
+ bgColor: bar,
181
+ },
182
+ () => {},
183
+ );
184
+ text('\n');
185
+ text(
186
+ color.fg(
187
+ 'silver',
188
+ 'Wrapped text respects ANSI colors and terminal width without escape damage.',
189
+ ),
190
+ );
191
+ },
192
+ );
193
+
194
+ Box(
195
+ ctx,
196
+ {
197
+ x: iconLab?.x,
198
+ y: iconLab?.y,
199
+ width: iconLab?.width,
200
+ height: iconLab?.height,
201
+ border: 'rounded',
202
+ borderColor: 'silver',
203
+ padding: [1, 2],
204
+ },
205
+ ({ text, table }) => {
206
+ text(color.fg('silver', color.bold('ICONS + TABLES\n\n')));
207
+ const rows = Object.keys(ICON_MAP)
208
+ .slice(0, 12)
209
+ .reduce<string[][]>((acc, name, index) => {
210
+ const row = Math.floor(index / 3);
211
+ acc[row] ??= [];
212
+ acc[row]!.push(`${icon(name)} ${color.dim(name)}`);
213
+ return acc;
214
+ }, []);
215
+
216
+ table(rows, {
217
+ width: Math.max(1, (iconLab?.width ?? 8) - 6),
218
+ border: 'none',
219
+ padding: [0, 1],
220
+ columns: [{ align: 'left' }, { align: 'left' }, { align: 'left' }],
221
+ });
222
+ },
223
+ );
224
+ });
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Bunti Terminal Capability Detection
3
+ *
4
+ * Env-first and synchronous: `identifyTerminal()` is a pure function from an
5
+ * environment map to a `TerminalProfile` (app, version, multiplexer,
6
+ * truecolor, sync-output support, Nerd Font policy). No escape-sequence
7
+ * probes are sent; async handshakes are deliberately deferred.
8
+ */
9
+ /** Legacy capability shape (kept for back-compat; see TerminalProfile). */
10
+ export interface TerminalCapabilities {
11
+ nerdFont: boolean;
12
+ glyphProtocol: boolean;
13
+ unicode: boolean;
14
+ color: boolean;
15
+ }
16
+ /** Terminal emulators Bunti identifies from environment variables. */
17
+ export type TerminalApp = 'ghostty' | 'kitty' | 'wezterm' | 'iterm2' | 'alacritty' | 'vscode' | 'apple-terminal' | 'warp' | 'unknown';
18
+ /**
19
+ * Nerd Font availability policy:
20
+ * - 'yes': the terminal embeds Nerd Font symbols (Ghostty >= 1.2).
21
+ * - 'assumed-yes': power-user terminals whose users overwhelmingly install
22
+ * patched fonts (kitty, WezTerm, iTerm2, Warp, Alacritty).
23
+ * - 'assumed-no': stock fonts, no embedded symbols (VS Code, Apple Terminal,
24
+ * unknown terminals) — Bunti falls back to the ASCII icon tier.
25
+ * - 'no': explicitly disabled via BUNTI_NF/NERD_FONTS.
26
+ */
27
+ export type NerdFontPolicy = 'yes' | 'no' | 'assumed-yes' | 'assumed-no';
28
+ /**
29
+ * Everything Bunti knows about the hosting terminal, derived purely from
30
+ * environment variables. Exposed as `ctx.terminal` (detected once per
31
+ * render()).
32
+ */
33
+ export interface TerminalProfile {
34
+ /** Identified terminal emulator ('unknown' when no signal matches). */
35
+ app: TerminalApp;
36
+ /** TERM_PROGRAM_VERSION / LC_TERMINAL_VERSION when available. */
37
+ version?: string;
38
+ /** Terminal multiplexer sitting between Bunti and the emulator. */
39
+ multiplexer?: 'tmux' | 'screen';
40
+ /** 24-bit color support (mirrors detectColorTier() === 'truecolor'). */
41
+ truecolor: boolean;
42
+ /** Known support for synchronized output (DEC private mode 2026). */
43
+ syncOutput: boolean;
44
+ /** Nerd Font glyph policy for the icon engine. */
45
+ nerdFont: NerdFontPolicy;
46
+ /** 'override' when BUNTI_NF/NERD_FONTS forced the nerdFont field. */
47
+ source: 'override' | 'env';
48
+ }
49
+ type Env = Record<string, string | undefined>;
50
+ /**
51
+ * Color capability tiers, from full 24-bit RGB down to no color at all.
52
+ * `resolveColor()` quantizes RGB values to the active tier automatically.
53
+ */
54
+ export type ColorTier = 'truecolor' | '256' | '16' | 'mono';
55
+ /**
56
+ * Pure env-to-profile detection (defaults to process.env). Never mutates the
57
+ * environment and sends no escape sequences, so it is safe to call anywhere.
58
+ *
59
+ * Detection order:
60
+ * 1. BUNTI_NF/NERD_FONTS/NERD_FONT force the nerdFont field on or off
61
+ * (source: 'override'); app identification still runs.
62
+ * 2. TMUX/screen are recorded as `multiplexer`; app identification then
63
+ * relies on variables that pass through the multiplexer.
64
+ * 3. App signals, in order: Ghostty (GHOSTTY_RESOURCES_DIR, lowercase
65
+ * TERM_PROGRAM=ghostty, TERM=xterm-ghostty), kitty (KITTY_WINDOW_ID,
66
+ * TERM=xterm-kitty), WezTerm (WEZTERM_PANE, TERM_PROGRAM), iTerm2
67
+ * (TERM_PROGRAM=iTerm.app, LC_TERMINAL=iTerm2), Alacritty
68
+ * (ALACRITTY_WINDOW_ID, TERM=alacritty), VS Code, Apple Terminal, Warp.
69
+ */
70
+ export declare function identifyTerminal(env?: Env): TerminalProfile;
71
+ /**
72
+ * Pure tier detection from an environment map (defaults to process.env).
73
+ *
74
+ * Rules, in order:
75
+ * 1. NO_COLOR set to a non-empty value -> 'mono' (https://no-color.org spec).
76
+ * 2. TERM=dumb -> 'mono'.
77
+ * 3. COLORTERM=truecolor|24bit -> 'truecolor'.
78
+ * 4. An identified truecolor-capable app (ghostty, kitty, wezterm, iterm2,
79
+ * alacritty, vscode, warp) -> 'truecolor'. This also catches TERM values
80
+ * like xterm-kitty that the legacy TERM families would misclassify.
81
+ * 5. Known truecolor TERM_PROGRAM, or a truecolor/direct TERM -> 'truecolor'.
82
+ * 6. TERM containing "256color" -> '256'.
83
+ * 7. Legacy TERM families (xterm, screen, vt100, linux, ...) -> '16'.
84
+ * 8. Otherwise -> 'truecolor' (optimistic: modern terminals rarely set hints).
85
+ */
86
+ export declare function detectColorTier(env?: Env): ColorTier;
87
+ /**
88
+ * Returns the active color tier. Detection runs lazily on first call and is
89
+ * cached; `setColorTier()` overrides it (used by ScreenOptions.colorTier and
90
+ * tests).
91
+ */
92
+ export declare function colorTier(): ColorTier;
93
+ /**
94
+ * Forces a color tier (pass undefined to clear the override and re-detect
95
+ * lazily on the next `colorTier()` call).
96
+ */
97
+ export declare function setColorTier(tier?: ColorTier): void;
98
+ /**
99
+ * Back-compat wrapper over `identifyTerminal()` returning the legacy
100
+ * TerminalCapabilities shape. Prefer `identifyTerminal()` / `ctx.terminal`.
101
+ */
102
+ export declare function detectCapabilities(env?: Env): Promise<TerminalCapabilities>;
103
+ export {};
104
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,2EAA2E;AAC3E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,sEAAsE;AACtE,MAAM,MAAM,WAAW,GACnB,SAAS,GACT,OAAO,GACP,SAAS,GACT,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,gBAAgB,GAChB,MAAM,GACN,SAAS,CAAC;AAEd;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,IAAI,GAAG,aAAa,GAAG,YAAY,CAAC;AAEzE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,GAAG,EAAE,WAAW,CAAC;IACjB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAChC,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAC;IACnB,qEAAqE;IACrE,UAAU,EAAE,OAAO,CAAC;IACpB,kDAAkD;IAClD,QAAQ,EAAE,cAAc,CAAC;IACzB,qEAAqE;IACrE,MAAM,EAAE,UAAU,GAAG,KAAK,CAAC;CAC5B;AAED,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAE9C;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,KAAK,GAAG,IAAI,GAAG,MAAM,CAAC;AAwH5D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,GAAE,GAAwB,GAC5B,eAAe,CAcjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,GAAwB,GAAG,SAAS,CAwBxE;AAKD;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,SAAS,CAIrC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,CAAC,EAAE,SAAS,QAG5C;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,GAAE,GAAwB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,CAQ/B"}
package/dist/detect.js ADDED
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Bunti Terminal Capability Detection
3
+ *
4
+ * Env-first and synchronous: `identifyTerminal()` is a pure function from an
5
+ * environment map to a `TerminalProfile` (app, version, multiplexer,
6
+ * truecolor, sync-output support, Nerd Font policy). No escape-sequence
7
+ * probes are sent; async handshakes are deliberately deferred.
8
+ */
9
+ /**
10
+ * TERM_PROGRAM values known to render 24-bit color but NOT part of the
11
+ * TerminalApp union (those are covered by app identification).
12
+ */
13
+ const EXTRA_TRUECOLOR_PROGRAMS = ['Hyper', 'Rio', 'Term7'];
14
+ /** Apps with known 24-bit color support (all identified apps but Apple Terminal). */
15
+ const TRUECOLOR_APPS = [
16
+ 'ghostty',
17
+ 'kitty',
18
+ 'wezterm',
19
+ 'iterm2',
20
+ 'alacritty',
21
+ 'vscode',
22
+ 'warp',
23
+ ];
24
+ /** Apps with known synchronized-output (mode 2026) support. */
25
+ const SYNC_OUTPUT_APPS = [
26
+ 'ghostty',
27
+ 'kitty',
28
+ 'wezterm',
29
+ 'iterm2',
30
+ 'alacritty',
31
+ 'warp',
32
+ ];
33
+ /** Nerd Font policy per identified app (see NerdFontPolicy). */
34
+ const NERD_FONT_POLICY = {
35
+ ghostty: 'yes', // embeds Symbols Nerd Font since 1.2
36
+ kitty: 'assumed-yes',
37
+ wezterm: 'assumed-yes',
38
+ iterm2: 'assumed-yes',
39
+ warp: 'assumed-yes',
40
+ alacritty: 'assumed-yes',
41
+ vscode: 'assumed-no',
42
+ 'apple-terminal': 'assumed-no',
43
+ unknown: 'assumed-no',
44
+ };
45
+ /** The TERM_PROGRAM value each app announces (when it sets one at all). */
46
+ const TERM_PROGRAM_BY_APP = {
47
+ ghostty: 'ghostty', // lowercase — Ghostty never capitalizes it
48
+ wezterm: 'WezTerm',
49
+ iterm2: 'iTerm.app',
50
+ vscode: 'vscode',
51
+ 'apple-terminal': 'Apple_Terminal',
52
+ warp: 'WarpTerminal',
53
+ };
54
+ /**
55
+ * Identifies the terminal app, version, and multiplexer from env vars.
56
+ * App-specific variables (GHOSTTY_RESOURCES_DIR, KITTY_WINDOW_ID, ...) are
57
+ * checked alongside TERM_PROGRAM because they survive tmux/screen, whose
58
+ * inner TERM rewrites would otherwise mask the outer emulator.
59
+ */
60
+ function detectApp(env) {
61
+ const term = env.TERM || '';
62
+ const termProgram = env.TERM_PROGRAM || '';
63
+ const multiplexer = env.TMUX
64
+ ? 'tmux'
65
+ : term.startsWith('tmux')
66
+ ? 'tmux'
67
+ : env.STY || term.startsWith('screen')
68
+ ? 'screen'
69
+ : undefined;
70
+ let app = 'unknown';
71
+ if (env.GHOSTTY_RESOURCES_DIR ||
72
+ termProgram === 'ghostty' ||
73
+ term === 'xterm-ghostty') {
74
+ app = 'ghostty';
75
+ }
76
+ else if (env.KITTY_WINDOW_ID || term === 'xterm-kitty') {
77
+ app = 'kitty';
78
+ }
79
+ else if (env.WEZTERM_PANE || termProgram === 'WezTerm') {
80
+ app = 'wezterm';
81
+ }
82
+ else if (termProgram === 'iTerm.app' || env.LC_TERMINAL === 'iTerm2') {
83
+ app = 'iterm2';
84
+ }
85
+ else if (env.ALACRITTY_WINDOW_ID || term === 'alacritty') {
86
+ app = 'alacritty';
87
+ }
88
+ else if (termProgram === 'vscode') {
89
+ app = 'vscode';
90
+ }
91
+ else if (termProgram === 'Apple_Terminal') {
92
+ app = 'apple-terminal';
93
+ }
94
+ else if (termProgram === 'WarpTerminal') {
95
+ app = 'warp';
96
+ }
97
+ let version;
98
+ if (termProgram !== '' && termProgram === TERM_PROGRAM_BY_APP[app]) {
99
+ version = env.TERM_PROGRAM_VERSION || undefined;
100
+ }
101
+ else if (app === 'iterm2' && env.LC_TERMINAL === 'iTerm2') {
102
+ version = env.LC_TERMINAL_VERSION || undefined;
103
+ }
104
+ return { app, version, multiplexer };
105
+ }
106
+ /**
107
+ * Parses the BUNTI_NF / NERD_FONTS / NERD_FONT override variables.
108
+ * Returns 'yes'/'no' when forced, undefined when unset or unrecognized.
109
+ */
110
+ function nerdFontOverride(env) {
111
+ const value = env.NERD_FONTS || env.NERD_FONT || env.BUNTI_NF;
112
+ if (value === '1' || value === 'true' || value === 'yes')
113
+ return 'yes';
114
+ if (value === '0' || value === 'false' || value === 'no')
115
+ return 'no';
116
+ return undefined;
117
+ }
118
+ /**
119
+ * Pure env-to-profile detection (defaults to process.env). Never mutates the
120
+ * environment and sends no escape sequences, so it is safe to call anywhere.
121
+ *
122
+ * Detection order:
123
+ * 1. BUNTI_NF/NERD_FONTS/NERD_FONT force the nerdFont field on or off
124
+ * (source: 'override'); app identification still runs.
125
+ * 2. TMUX/screen are recorded as `multiplexer`; app identification then
126
+ * relies on variables that pass through the multiplexer.
127
+ * 3. App signals, in order: Ghostty (GHOSTTY_RESOURCES_DIR, lowercase
128
+ * TERM_PROGRAM=ghostty, TERM=xterm-ghostty), kitty (KITTY_WINDOW_ID,
129
+ * TERM=xterm-kitty), WezTerm (WEZTERM_PANE, TERM_PROGRAM), iTerm2
130
+ * (TERM_PROGRAM=iTerm.app, LC_TERMINAL=iTerm2), Alacritty
131
+ * (ALACRITTY_WINDOW_ID, TERM=alacritty), VS Code, Apple Terminal, Warp.
132
+ */
133
+ export function identifyTerminal(env = process.env) {
134
+ const { app, version, multiplexer } = detectApp(env);
135
+ const override = nerdFontOverride(env);
136
+ const profile = {
137
+ app,
138
+ truecolor: detectColorTier(env) === 'truecolor',
139
+ syncOutput: SYNC_OUTPUT_APPS.includes(app),
140
+ nerdFont: override ?? NERD_FONT_POLICY[app],
141
+ source: override !== undefined ? 'override' : 'env',
142
+ };
143
+ if (version !== undefined)
144
+ profile.version = version;
145
+ if (multiplexer !== undefined)
146
+ profile.multiplexer = multiplexer;
147
+ return profile;
148
+ }
149
+ /**
150
+ * Pure tier detection from an environment map (defaults to process.env).
151
+ *
152
+ * Rules, in order:
153
+ * 1. NO_COLOR set to a non-empty value -> 'mono' (https://no-color.org spec).
154
+ * 2. TERM=dumb -> 'mono'.
155
+ * 3. COLORTERM=truecolor|24bit -> 'truecolor'.
156
+ * 4. An identified truecolor-capable app (ghostty, kitty, wezterm, iterm2,
157
+ * alacritty, vscode, warp) -> 'truecolor'. This also catches TERM values
158
+ * like xterm-kitty that the legacy TERM families would misclassify.
159
+ * 5. Known truecolor TERM_PROGRAM, or a truecolor/direct TERM -> 'truecolor'.
160
+ * 6. TERM containing "256color" -> '256'.
161
+ * 7. Legacy TERM families (xterm, screen, vt100, linux, ...) -> '16'.
162
+ * 8. Otherwise -> 'truecolor' (optimistic: modern terminals rarely set hints).
163
+ */
164
+ export function detectColorTier(env = process.env) {
165
+ if (env.NO_COLOR !== undefined && env.NO_COLOR !== '')
166
+ return 'mono';
167
+ const term = env.TERM || '';
168
+ if (term === 'dumb')
169
+ return 'mono';
170
+ const colorterm = (env.COLORTERM || '').toLowerCase();
171
+ if (colorterm === 'truecolor' || colorterm === '24bit')
172
+ return 'truecolor';
173
+ const { app } = detectApp(env);
174
+ if (TRUECOLOR_APPS.includes(app))
175
+ return 'truecolor';
176
+ if (EXTRA_TRUECOLOR_PROGRAMS.includes(env.TERM_PROGRAM || '')) {
177
+ return 'truecolor';
178
+ }
179
+ if (term.includes('truecolor') ||
180
+ term.includes('24bit') ||
181
+ term.includes('direct')) {
182
+ return 'truecolor';
183
+ }
184
+ if (term.includes('256color'))
185
+ return '256';
186
+ if (/^(xterm|screen|tmux|vt(1|2)\d\d|rxvt|linux|ansi|cygwin)/.test(term)) {
187
+ return '16';
188
+ }
189
+ return 'truecolor';
190
+ }
191
+ let tierOverride;
192
+ let detectedTier;
193
+ /**
194
+ * Returns the active color tier. Detection runs lazily on first call and is
195
+ * cached; `setColorTier()` overrides it (used by ScreenOptions.colorTier and
196
+ * tests).
197
+ */
198
+ export function colorTier() {
199
+ if (tierOverride !== undefined)
200
+ return tierOverride;
201
+ if (detectedTier === undefined)
202
+ detectedTier = detectColorTier();
203
+ return detectedTier;
204
+ }
205
+ /**
206
+ * Forces a color tier (pass undefined to clear the override and re-detect
207
+ * lazily on the next `colorTier()` call).
208
+ */
209
+ export function setColorTier(tier) {
210
+ tierOverride = tier;
211
+ if (tier === undefined)
212
+ detectedTier = undefined;
213
+ }
214
+ /**
215
+ * Back-compat wrapper over `identifyTerminal()` returning the legacy
216
+ * TerminalCapabilities shape. Prefer `identifyTerminal()` / `ctx.terminal`.
217
+ */
218
+ export async function detectCapabilities(env = process.env) {
219
+ const profile = identifyTerminal(env);
220
+ return {
221
+ nerdFont: profile.nerdFont === 'yes' || profile.nerdFont === 'assumed-yes',
222
+ glyphProtocol: profile.app === 'ghostty',
223
+ unicode: true,
224
+ color: detectColorTier(env) !== 'mono',
225
+ };
226
+ }
227
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Bunti Dev Diagnostics
3
+ *
4
+ * Non-fatal "you probably meant" hints that teach the API while you build.
5
+ * Hints are buffered (deduplicated) and flushed ONCE to stderr when the
6
+ * process exits — never mid-frame, so a warning can never tear an active
7
+ * render. All hints are suppressed when NODE_ENV=production or
8
+ * BUNTI_NO_HINTS=1.
9
+ *
10
+ * The icon engine's unknown-name warnings flow through the same buffer via
11
+ * registerHintFlusher(), so a session produces at most one hint block.
12
+ */
13
+ import type { Rect } from './geometry';
14
+ import type { ScreenOptions } from './state';
15
+ /** Lazily produces extra hint lines at flush time (used by the icon engine). */
16
+ type HintFlusher = () => string[];
17
+ /** True when dev hints are disabled (NODE_ENV=production or BUNTI_NO_HINTS=1). */
18
+ export declare function hintsSuppressed(): boolean;
19
+ /**
20
+ * Buffers a one-line dev hint (deduplicated by message). Never writes to
21
+ * the terminal directly — everything drains on process exit.
22
+ */
23
+ export declare function recordHint(message: string): void;
24
+ /**
25
+ * Registers a lazy hint-line producer drained at flush time. Lets modules
26
+ * with their own buffers (icons) share the single exit flush.
27
+ */
28
+ export declare function registerHintFlusher(flusher: HintFlusher): void;
29
+ /** Renders and clears every buffered hint; '' when there are none. */
30
+ export declare function drainHints(): string;
31
+ /** Exit-hook target: writes all buffered hints to stderr, once. */
32
+ export declare function flushHints(): void;
33
+ /** Capped two-row Levenshtein; returns cap+1 as soon as it can bail out. */
34
+ export declare function editDistance(a: string, b: string, cap: number): number;
35
+ /**
36
+ * Best "did you mean" candidate for a name: exact match wins, then
37
+ * prefix relationships, then capped edit distance (cap scales with
38
+ * length). Returns undefined when nothing is close enough.
39
+ */
40
+ export declare function nearestMatch(target: string, candidates: Iterable<string>): string | undefined;
41
+ /**
42
+ * Called on reads of ctx.lastKey / ctx.keys / keyPressed / isKeyHeld:
43
+ * hints once when neither keyboard nor mouse input was enabled, so the
44
+ * app can never receive what it is polling for.
45
+ */
46
+ export declare function hintInputReadWithoutKeyboard(options: ScreenOptions): void;
47
+ /**
48
+ * Called by the render loop: keyboard input only works on a real TTY —
49
+ * piped stdin (CI, `echo | bun app.ts`, agent harnesses) is ignored.
50
+ */
51
+ export declare function hintKeyboardStdinNotTTY(keyboardRequested: boolean, stdinIsTTY: boolean): void;
52
+ /**
53
+ * Marks a frame boundary for keyless-hook tracking: compares the two most
54
+ * recent completed frames and hints once when the positional hook count
55
+ * shrank or a slot changed type (useState <-> useAsync) — the signature of
56
+ * conditionally-called keyless hooks returning another call's state.
57
+ */
58
+ export declare function beginHookFrame(state: object): void;
59
+ /** Records one keyless hook call (positional index + hook kind). */
60
+ export declare function recordKeylessHook(state: object, index: number, kind: 'useState' | 'useAsync'): void;
61
+ /**
62
+ * Cheap per-frame heuristic run by flushFlow: top-level boxes paint
63
+ * directly to the screen buffer, so two intersecting rects mean one box
64
+ * painted over another with nothing controlling the stacking. Hints once,
65
+ * then stops checking. O(n²) over at most 50 rects per frame.
66
+ */
67
+ export declare function hintOverlappingBoxes(rects: Rect[] | undefined): void;
68
+ /** @internal Test hook: currently buffered hint messages. */
69
+ export declare function __bufferedHints(): string[];
70
+ /** @internal Test hook: clears buffered hints and one-shot latches. */
71
+ export declare function __resetHints(): void;
72
+ export {};
73
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,gFAAgF;AAChF,KAAK,WAAW,GAAG,MAAM,MAAM,EAAE,CAAC;AAMlC,kFAAkF;AAClF,wBAAgB,eAAe,IAAI,OAAO,CAIzC;AASD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAIhD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAG9D;AAED,sEAAsE;AACtE,wBAAgB,UAAU,IAAI,MAAM,CAOnC;AAED,mEAAmE;AACnE,wBAAgB,UAAU,IAAI,IAAI,CAIjC;AAID,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAiBtE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,GAC3B,MAAM,GAAG,SAAS,CAqBpB;AAID;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAKzE;AAID;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,iBAAiB,EAAE,OAAO,EAC1B,UAAU,EAAE,OAAO,GAClB,IAAI,CAMN;AAgBD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAsBlD;AAED,oEAAoE;AACpE,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,UAAU,GAAG,UAAU,GAC5B,IAAI,CAKN;AAQD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,SAAS,GAAG,IAAI,CAwBpE;AAID,6DAA6D;AAC7D,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAE1C;AAED,uEAAuE;AACvE,wBAAgB,YAAY,IAAI,IAAI,CAGnC"}