claude-statusline 2.1.2

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 (48) hide show
  1. package/LICENSE +203 -0
  2. package/README.md +362 -0
  3. package/bin/claude-statusline +22 -0
  4. package/dist/core/cache.d.ts +67 -0
  5. package/dist/core/cache.js +223 -0
  6. package/dist/core/cache.js.map +1 -0
  7. package/dist/core/config.d.ts +190 -0
  8. package/dist/core/config.js +192 -0
  9. package/dist/core/config.js.map +1 -0
  10. package/dist/core/security.d.ts +27 -0
  11. package/dist/core/security.js +154 -0
  12. package/dist/core/security.js.map +1 -0
  13. package/dist/env/context.d.ts +92 -0
  14. package/dist/env/context.js +295 -0
  15. package/dist/env/context.js.map +1 -0
  16. package/dist/git/native.d.ts +35 -0
  17. package/dist/git/native.js +141 -0
  18. package/dist/git/native.js.map +1 -0
  19. package/dist/git/status.d.ts +65 -0
  20. package/dist/git/status.js +256 -0
  21. package/dist/git/status.js.map +1 -0
  22. package/dist/index.bundle.js +11 -0
  23. package/dist/index.d.ts +9 -0
  24. package/dist/index.js +396 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/metafile.prod.json +473 -0
  27. package/dist/ui/symbols.d.ts +31 -0
  28. package/dist/ui/symbols.js +308 -0
  29. package/dist/ui/symbols.js.map +1 -0
  30. package/dist/ui/width.d.ts +29 -0
  31. package/dist/ui/width.js +261 -0
  32. package/dist/ui/width.js.map +1 -0
  33. package/dist/utils/runtime.d.ts +31 -0
  34. package/dist/utils/runtime.js +82 -0
  35. package/dist/utils/runtime.js.map +1 -0
  36. package/docs/ARCHITECTURE.md +336 -0
  37. package/docs/FEATURE_COMPARISON.md +178 -0
  38. package/docs/MIGRATION.md +354 -0
  39. package/docs/README.md +101 -0
  40. package/docs/eval-01-terminal-widths.md +519 -0
  41. package/docs/guide-01-configuration.md +277 -0
  42. package/docs/guide-02-troubleshooting.md +416 -0
  43. package/docs/guide-03-performance.md +183 -0
  44. package/docs/prd-01-typescript-perf-optimization.md +480 -0
  45. package/docs/research-01-sandbox-detection.md +169 -0
  46. package/docs/research-02-competitive-analysis.md +226 -0
  47. package/docs/research-03-platform-analysis.md +142 -0
  48. package/package.json +89 -0
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Symbol detection cache - symbols don't change during runtime
3
+ * Added cache version to handle potential stale data issues
4
+ */
5
+ const symbolCache = new Map();
6
+ const CACHE_VERSION = 1; // Increment to invalidate all caches
7
+ /**
8
+ * ASCII symbol set (fallback)
9
+ */
10
+ const ASCII_SYMBOLS = {
11
+ git: '@',
12
+ model: '*',
13
+ contextWindow: '#',
14
+ staged: '+',
15
+ conflict: 'C',
16
+ stashed: '$',
17
+ ahead: 'A',
18
+ behind: 'B',
19
+ diverged: 'D',
20
+ renamed: '>',
21
+ deleted: 'X',
22
+ };
23
+ /**
24
+ * Nerd Font symbol set (enhanced)
25
+ */
26
+ const NERD_FONT_SYMBOLS = {
27
+ git: '',
28
+ model: '󰚩',
29
+ contextWindow: '⚡︎',
30
+ staged: '+',
31
+ conflict: '×',
32
+ stashed: '⚑',
33
+ ahead: '⇡',
34
+ behind: '⇣',
35
+ diverged: '⇕',
36
+ renamed: '»',
37
+ deleted: '✘',
38
+ };
39
+ /**
40
+ * Detect Nerd Font support and return appropriate symbols (with caching)
41
+ */
42
+ export async function detectSymbols(config) {
43
+ // Create cache key based on config and environment
44
+ const envFingerprint = process.env.NERD_FONT + '|' + process.env.TERM_PROGRAM + '|' + process.env.TERM;
45
+ const cacheKey = `${CACHE_VERSION}:${config.noEmoji ? 'ascii' : 'nerd'}:${envFingerprint}`;
46
+ // Check cache first with timestamp validation
47
+ const cached = symbolCache.get(cacheKey);
48
+ if (cached && Date.now() - cached.timestamp < 60000) { // 1 minute cache TTL
49
+ return cached.symbols;
50
+ }
51
+ let symbols;
52
+ // If emoji/nerd font is explicitly disabled, use ASCII
53
+ if (config.noEmoji) {
54
+ symbols = { ...ASCII_SYMBOLS, ...config.symbols, ...config.asciiSymbols };
55
+ }
56
+ else {
57
+ // Try to detect Nerd Font support
58
+ const detection = await detectNerdFontSupport();
59
+ if (detection.hasNerdFont) {
60
+ // Merge user's custom symbols with Nerd Font defaults
61
+ symbols = { ...NERD_FONT_SYMBOLS, ...config.symbols };
62
+ }
63
+ else {
64
+ // Merge user's custom ASCII symbols with ASCII defaults
65
+ symbols = { ...ASCII_SYMBOLS, ...config.asciiSymbols };
66
+ }
67
+ }
68
+ // Cache the result with timestamp
69
+ symbolCache.set(cacheKey, { symbols, timestamp: Date.now() });
70
+ return symbols;
71
+ }
72
+ /**
73
+ * Comprehensive Nerd Font support detection
74
+ */
75
+ async function detectNerdFontSupport() {
76
+ const terminalInfo = {
77
+ hasNerdFont: false,
78
+ terminal: '',
79
+ font: '',
80
+ method: '',
81
+ };
82
+ // Method 1: Environment variable NERD_FONT=1
83
+ if (process.env.NERD_FONT === '1') {
84
+ terminalInfo.hasNerdFont = true;
85
+ terminalInfo.method = 'NERD_FONT env var';
86
+ return terminalInfo;
87
+ }
88
+ // Method 2: Terminal program detection
89
+ const termProgram = process.env.TERM_PROGRAM;
90
+ const term = process.env.TERM;
91
+ if (termProgram) {
92
+ terminalInfo.terminal = termProgram;
93
+ terminalInfo.method = 'TERM_PROGRAM detection';
94
+ // These terminals commonly have Nerd Font support
95
+ const nerdFontTerminals = ['vscode', 'ghostty', 'wezterm', 'iterm'];
96
+ if (nerdFontTerminals.includes(termProgram)) {
97
+ terminalInfo.hasNerdFont = true;
98
+ return terminalInfo;
99
+ }
100
+ }
101
+ if (term) {
102
+ terminalInfo.terminal = term;
103
+ terminalInfo.method = 'TERM detection';
104
+ // These terminal types commonly support Nerd Fonts
105
+ const nerdFontTerms = ['alacritty', 'kitty', 'wezterm', 'ghostty', 'xterm-256color'];
106
+ if (nerdFontTerms.includes(term)) {
107
+ terminalInfo.hasNerdFont = true;
108
+ return terminalInfo;
109
+ }
110
+ }
111
+ // Method 3: Try to detect via font list (Unix/Linux/macOS)
112
+ const fontDetection = await detectViaFontList();
113
+ if (fontDetection.hasNerdFont) {
114
+ terminalInfo.hasNerdFont = true;
115
+ terminalInfo.font = fontDetection.font;
116
+ terminalInfo.method = 'font list detection';
117
+ return terminalInfo;
118
+ }
119
+ // Method 4: Check for common Nerd Font installation patterns
120
+ const installDetection = await detectNerdFontInstallation();
121
+ if (installDetection.hasNerdFont) {
122
+ terminalInfo.hasNerdFont = true;
123
+ terminalInfo.font = installDetection.font;
124
+ terminalInfo.method = 'installation detection';
125
+ return terminalInfo;
126
+ }
127
+ // Method 5: Check environment variables that might indicate font support
128
+ const envDetection = detectFromEnvironment();
129
+ if (envDetection.hasNerdFont) {
130
+ terminalInfo.hasNerdFont = true;
131
+ terminalInfo.method = 'environment detection';
132
+ return terminalInfo;
133
+ }
134
+ // Method 6: Platform-specific detection
135
+ const platformDetection = await detectPlatformSpecific();
136
+ if (platformDetection.hasNerdFont) {
137
+ terminalInfo.hasNerdFont = true;
138
+ terminalInfo.method = 'platform-specific detection';
139
+ return terminalInfo;
140
+ }
141
+ // Default: no Nerd Font detected
142
+ return terminalInfo;
143
+ }
144
+ /**
145
+ * Detect Nerd Font via font list command
146
+ */
147
+ async function detectViaFontList() {
148
+ try {
149
+ const { exec } = await import('child_process');
150
+ const { promisify } = await import('util');
151
+ const execAsync = promisify(exec);
152
+ // Try fc-list (Linux) or system_profiler (macOS)
153
+ let fontListCommand = '';
154
+ const platform = process.platform;
155
+ if (platform === 'linux') {
156
+ fontListCommand = 'fc-list';
157
+ }
158
+ else if (platform === 'darwin') {
159
+ fontListCommand = 'system_profiler SPFontsDataType 2>/dev/null || system_profiler SPFontsDataType';
160
+ }
161
+ if (fontListCommand) {
162
+ const { stdout } = await execAsync(fontListCommand, {
163
+ timeout: 3000,
164
+ encoding: 'utf-8',
165
+ });
166
+ const nerdFontPatterns = [
167
+ /nerd font/i,
168
+ /symbols only/i,
169
+ /jetbrains mono.*nerd/i,
170
+ /fira code.*nerd/i,
171
+ /hack.*nerd/i,
172
+ /source code pro.*nerd/i,
173
+ /ubuntu mono.*nerd/i,
174
+ /anonymous pro.*nerd/i,
175
+ ];
176
+ for (const pattern of nerdFontPatterns) {
177
+ if (pattern.test(stdout)) {
178
+ // Try to extract font name
179
+ const match = stdout.match(/([^:\n]*)(?=\s*(nerd|symbols))/i);
180
+ const fontName = match ? match[1] : 'Nerd Font';
181
+ return { hasNerdFont: true, font: fontName?.trim() || 'Nerd Font' };
182
+ }
183
+ }
184
+ }
185
+ }
186
+ catch {
187
+ // Font detection failed
188
+ }
189
+ return { hasNerdFont: false, font: '' };
190
+ }
191
+ /**
192
+ * Detect Nerd Font installation in common locations
193
+ */
194
+ async function detectNerdFontInstallation() {
195
+ try {
196
+ const { access, readdir } = await import('fs/promises');
197
+ const { homedir } = await import('os');
198
+ const platform = process.platform;
199
+ const fontPaths = [];
200
+ if (platform === 'darwin') {
201
+ fontPaths.push(`${homedir()}/Library/Fonts`, '/System/Library/Fonts', '/Library/Fonts');
202
+ }
203
+ else if (platform === 'linux') {
204
+ fontPaths.push(`${homedir()}/.local/share/fonts`, `${homedir()}/.fonts`, '/usr/share/fonts', '/usr/local/share/fonts');
205
+ }
206
+ const nerdFontNames = [
207
+ 'jetbrains-mono-nerd-font',
208
+ 'fira-code-nerd-font',
209
+ 'hack-nerd-font',
210
+ 'source-code-pro-nerd-font',
211
+ 'ubuntu-mono-nerd-font',
212
+ 'anonymous-pro-nerd-font',
213
+ ];
214
+ for (const fontPath of fontPaths) {
215
+ try {
216
+ await access(fontPath);
217
+ const files = await readdir(fontPath);
218
+ for (const file of files) {
219
+ const fileName = file.toLowerCase();
220
+ for (const nerdFontName of nerdFontNames) {
221
+ if (fileName.includes(nerdFontName)) {
222
+ return { hasNerdFont: true, font: file };
223
+ }
224
+ }
225
+ }
226
+ }
227
+ catch {
228
+ // Can't access this font directory
229
+ }
230
+ }
231
+ }
232
+ catch {
233
+ // Font detection failed
234
+ }
235
+ return { hasNerdFont: false, font: '' };
236
+ }
237
+ /**
238
+ * Detect Nerd Font support from environment variables
239
+ */
240
+ function detectFromEnvironment() {
241
+ // Check for various environment variables that might indicate Nerd Font support
242
+ const nerdFontEnvVars = [
243
+ 'POWERLINE_COMMAND', // Often used with Nerd Fonts
244
+ 'NERDFONTS', // Some terminals set this
245
+ 'FONT_FAMILY', // Some terminals expose the current font
246
+ ];
247
+ for (const envVar of nerdFontEnvVars) {
248
+ const value = process.env[envVar];
249
+ if (value && value.toLowerCase().includes('nerd')) {
250
+ return { hasNerdFont: true };
251
+ }
252
+ }
253
+ // Check if we're in a development environment that likely has Nerd Fonts
254
+ if (process.env.VSCODE_PID ||
255
+ process.env.TERM_PROGRAM === 'vscode' ||
256
+ process.env.TERM_PROGRAM === 'ghostty' ||
257
+ process.env.TERM_PROGRAM === 'wezterm') {
258
+ return { hasNerdFont: true };
259
+ }
260
+ return { hasNerdFont: false };
261
+ }
262
+ /**
263
+ * Platform-specific Nerd Font detection
264
+ */
265
+ async function detectPlatformSpecific() {
266
+ const platform = process.platform;
267
+ if (platform === 'darwin') {
268
+ // macOS: check if we're in a common development environment
269
+ try {
270
+ const { exec } = await import('child_process');
271
+ const { promisify } = await import('util');
272
+ const execAsync = promisify(exec);
273
+ // Check for Homebrew-installed Nerd Fonts
274
+ const { stdout } = await execAsync('brew list | grep -i font', {
275
+ timeout: 2000,
276
+ encoding: 'utf-8',
277
+ });
278
+ if (stdout.includes('nerd')) {
279
+ return { hasNerdFont: true };
280
+ }
281
+ }
282
+ catch {
283
+ // brew command failed or not available
284
+ }
285
+ }
286
+ return { hasNerdFont: false };
287
+ }
288
+ /**
289
+ * Get environment symbols with version information
290
+ */
291
+ export function getEnvironmentSymbols(symbolSet) {
292
+ return {
293
+ node: '', // Node.js
294
+ python: '', // Python
295
+ docker: '', // Docker
296
+ git: symbolSet.git,
297
+ model: symbolSet.model,
298
+ };
299
+ }
300
+ /**
301
+ * Test if symbols can be displayed properly
302
+ */
303
+ export async function testSymbolDisplay(_symbols) {
304
+ // In a terminal environment, we can't easily test if symbols display correctly
305
+ // For now, we'll assume that if we detected Nerd Font support, they can be displayed
306
+ return true;
307
+ }
308
+ //# sourceMappingURL=symbols.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols.js","sourceRoot":"","sources":["../../src/ui/symbols.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,WAAW,GAAG,IAAI,GAAG,EAAqD,CAAC;AACjF,MAAM,aAAa,GAAG,CAAC,CAAC,CAAC,qCAAqC;AAmB9D;;GAEG;AACH,MAAM,aAAa,GAAc;IAC/B,GAAG,EAAE,GAAG;IACR,KAAK,EAAE,GAAG;IACV,aAAa,EAAE,GAAG;IAClB,MAAM,EAAE,GAAG;IACX,QAAQ,EAAE,GAAG;IACb,OAAO,EAAE,GAAG;IACZ,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,QAAQ,EAAE,GAAG;IACb,OAAO,EAAE,GAAG;IACZ,OAAO,EAAE,GAAG;CACb,CAAC;AAEF;;GAEG;AACH,MAAM,iBAAiB,GAAc;IACnC,GAAG,EAAE,GAAG;IACR,KAAK,EAAE,IAAI;IACX,aAAa,EAAE,IAAI;IACnB,MAAM,EAAE,GAAG;IACX,QAAQ,EAAE,GAAG;IACb,OAAO,EAAE,GAAG;IACZ,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,QAAQ,EAAE,GAAG;IACb,OAAO,EAAE,GAAG;IACZ,OAAO,EAAE,GAAG;CACb,CAAC;AAYF;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc;IAChD,mDAAmD;IACnD,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,GAAG,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IACvG,MAAM,QAAQ,GAAG,GAAG,aAAa,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,IAAI,cAAc,EAAE,CAAC;IAE3F,8CAA8C;IAC9C,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,KAAK,EAAE,CAAC,CAAC,qBAAqB;QAC1E,OAAO,MAAM,CAAC,OAAO,CAAC;IACxB,CAAC;IAED,IAAI,OAAkB,CAAC;IAEvB,uDAAuD;IACvD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC;IAC5E,CAAC;SAAM,CAAC;QACN,kCAAkC;QAClC,MAAM,SAAS,GAAG,MAAM,qBAAqB,EAAE,CAAC;QAEhD,IAAI,SAAS,CAAC,WAAW,EAAE,CAAC;YAC1B,sDAAsD;YACtD,OAAO,GAAG,EAAE,GAAG,iBAAiB,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;QACxD,CAAC;aAAM,CAAC;YACN,wDAAwD;YACxD,OAAO,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC;QACzD,CAAC;IACH,CAAC;IAED,kCAAkC;IAClC,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAC9D,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,qBAAqB;IAClC,MAAM,YAAY,GAAiB;QACjC,WAAW,EAAE,KAAK;QAClB,QAAQ,EAAE,EAAE;QACZ,IAAI,EAAE,EAAE;QACR,MAAM,EAAE,EAAE;KACX,CAAC;IAEF,6CAA6C;IAC7C,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,GAAG,EAAE,CAAC;QAClC,YAAY,CAAC,WAAW,GAAG,IAAI,CAAC;QAChC,YAAY,CAAC,MAAM,GAAG,mBAAmB,CAAC;QAC1C,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,uCAAuC;IACvC,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IAE9B,IAAI,WAAW,EAAE,CAAC;QAChB,YAAY,CAAC,QAAQ,GAAG,WAAW,CAAC;QACpC,YAAY,CAAC,MAAM,GAAG,wBAAwB,CAAC;QAE/C,kDAAkD;QAClD,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QACpE,IAAI,iBAAiB,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5C,YAAY,CAAC,WAAW,GAAG,IAAI,CAAC;YAChC,OAAO,YAAY,CAAC;QACtB,CAAC;IACH,CAAC;IAED,IAAI,IAAI,EAAE,CAAC;QACT,YAAY,CAAC,QAAQ,GAAG,IAAI,CAAC;QAC7B,YAAY,CAAC,MAAM,GAAG,gBAAgB,CAAC;QAEvC,mDAAmD;QACnD,MAAM,aAAa,GAAG,CAAC,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;QACrF,IAAI,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,YAAY,CAAC,WAAW,GAAG,IAAI,CAAC;YAChC,OAAO,YAAY,CAAC;QACtB,CAAC;IACH,CAAC;IAED,2DAA2D;IAC3D,MAAM,aAAa,GAAG,MAAM,iBAAiB,EAAE,CAAC;IAChD,IAAI,aAAa,CAAC,WAAW,EAAE,CAAC;QAC9B,YAAY,CAAC,WAAW,GAAG,IAAI,CAAC;QAChC,YAAY,CAAC,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC;QACvC,YAAY,CAAC,MAAM,GAAG,qBAAqB,CAAC;QAC5C,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,6DAA6D;IAC7D,MAAM,gBAAgB,GAAG,MAAM,0BAA0B,EAAE,CAAC;IAC5D,IAAI,gBAAgB,CAAC,WAAW,EAAE,CAAC;QACjC,YAAY,CAAC,WAAW,GAAG,IAAI,CAAC;QAChC,YAAY,CAAC,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC;QAC1C,YAAY,CAAC,MAAM,GAAG,wBAAwB,CAAC;QAC/C,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,yEAAyE;IACzE,MAAM,YAAY,GAAG,qBAAqB,EAAE,CAAC;IAC7C,IAAI,YAAY,CAAC,WAAW,EAAE,CAAC;QAC7B,YAAY,CAAC,WAAW,GAAG,IAAI,CAAC;QAChC,YAAY,CAAC,MAAM,GAAG,uBAAuB,CAAC;QAC9C,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,wCAAwC;IACxC,MAAM,iBAAiB,GAAG,MAAM,sBAAsB,EAAE,CAAC;IACzD,IAAI,iBAAiB,CAAC,WAAW,EAAE,CAAC;QAClC,YAAY,CAAC,WAAW,GAAG,IAAI,CAAC;QAChC,YAAY,CAAC,MAAM,GAAG,6BAA6B,CAAC;QACpD,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,iCAAiC;IACjC,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,iBAAiB;IAC9B,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QAC/C,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAElC,iDAAiD;QACjD,IAAI,eAAe,GAAG,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAElC,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YACzB,eAAe,GAAG,SAAS,CAAC;QAC9B,CAAC;aAAM,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,eAAe,GAAG,gFAAgF,CAAC;QACrG,CAAC;QAED,IAAI,eAAe,EAAE,CAAC;YACpB,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,eAAe,EAAE;gBAClD,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;YAEH,MAAM,gBAAgB,GAAG;gBACvB,YAAY;gBACZ,eAAe;gBACf,uBAAuB;gBACvB,kBAAkB;gBAClB,aAAa;gBACb,wBAAwB;gBACxB,oBAAoB;gBACpB,sBAAsB;aACvB,CAAC;YAEF,KAAK,MAAM,OAAO,IAAI,gBAAgB,EAAE,CAAC;gBACvC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;oBACzB,2BAA2B;oBAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;oBAC9D,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;oBAChD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC;gBACtE,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,0BAA0B;IACvC,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACxD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QAEvC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAClC,MAAM,SAAS,GAAa,EAAE,CAAC;QAE/B,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC1B,SAAS,CAAC,IAAI,CACZ,GAAG,OAAO,EAAE,gBAAgB,EAC5B,uBAAuB,EACvB,gBAAgB,CACjB,CAAC;QACJ,CAAC;aAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YAChC,SAAS,CAAC,IAAI,CACZ,GAAG,OAAO,EAAE,qBAAqB,EACjC,GAAG,OAAO,EAAE,SAAS,EACrB,kBAAkB,EAClB,wBAAwB,CACzB,CAAC;QACJ,CAAC;QAED,MAAM,aAAa,GAAG;YACpB,0BAA0B;YAC1B,qBAAqB;YACrB,gBAAgB;YAChB,2BAA2B;YAC3B,uBAAuB;YACvB,yBAAyB;SAC1B,CAAC;QAEF,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACvB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAEtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;oBACpC,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;wBACzC,IAAI,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;4BACpC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;wBAC3C,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,mCAAmC;YACrC,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,SAAS,qBAAqB;IAC5B,gFAAgF;IAChF,MAAM,eAAe,GAAG;QACtB,mBAAmB,EAAE,6BAA6B;QAClD,WAAW,EAAE,0BAA0B;QACvC,aAAa,EAAE,yCAAyC;KACzD,CAAC;IAEF,KAAK,MAAM,MAAM,IAAI,eAAe,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,IACE,OAAO,CAAC,GAAG,CAAC,UAAU;QACtB,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,QAAQ;QACrC,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,SAAS;QACtC,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,SAAS,EACtC,CAAC;QACD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAC/B,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,sBAAsB;IACnC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAElC,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,4DAA4D;QAC5D,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;YAC/C,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;YAElC,0CAA0C;YAC1C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,0BAA0B,EAAE;gBAC7D,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;YAEH,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5B,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;YAC/B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;IACH,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAoB;IACxD,OAAO;QACL,IAAI,EAAE,GAAG,EAAE,UAAU;QACrB,MAAM,EAAE,GAAG,EAAE,SAAS;QACtB,MAAM,EAAE,GAAG,EAAE,SAAS;QACtB,GAAG,EAAE,SAAS,CAAC,GAAG;QAClB,KAAK,EAAE,SAAS,CAAC,KAAK;KACvB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAmB;IACzD,+EAA+E;IAC/E,qFAAqF;IACrF,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import { Config } from '../core/config.js';\n\n/**\n * Symbol detection cache - symbols don't change during runtime\n * Added cache version to handle potential stale data issues\n */\nconst symbolCache = new Map<string, { symbols: SymbolSet; timestamp: number }>();\nconst CACHE_VERSION = 1; // Increment to invalidate all caches\n\n/**\n * Symbol configuration interface\n */\nexport interface SymbolSet {\n git: string;\n model: string;\n contextWindow: string;\n staged: string;\n conflict: string;\n stashed: string;\n ahead: string;\n behind: string;\n diverged: string;\n renamed: string;\n deleted: string;\n}\n\n/**\n * ASCII symbol set (fallback)\n */\nconst ASCII_SYMBOLS: SymbolSet = {\n git: '@',\n model: '*',\n contextWindow: '#',\n staged: '+',\n conflict: 'C',\n stashed: '$',\n ahead: 'A',\n behind: 'B',\n diverged: 'D',\n renamed: '>',\n deleted: 'X',\n};\n\n/**\n * Nerd Font symbol set (enhanced)\n */\nconst NERD_FONT_SYMBOLS: SymbolSet = {\n git: '',\n model: '󰚩',\n contextWindow: '⚡︎',\n staged: '+',\n conflict: '×',\n stashed: '⚑',\n ahead: '⇡',\n behind: '⇣',\n diverged: '⇕',\n renamed: '»',\n deleted: '✘',\n};\n\n/**\n * Terminal detection results\n */\ninterface TerminalInfo {\n hasNerdFont: boolean;\n terminal: string;\n font: string;\n method: string;\n}\n\n/**\n * Detect Nerd Font support and return appropriate symbols (with caching)\n */\nexport async function detectSymbols(config: Config): Promise<SymbolSet> {\n // Create cache key based on config and environment\n const envFingerprint = process.env.NERD_FONT + '|' + process.env.TERM_PROGRAM + '|' + process.env.TERM;\n const cacheKey = `${CACHE_VERSION}:${config.noEmoji ? 'ascii' : 'nerd'}:${envFingerprint}`;\n\n // Check cache first with timestamp validation\n const cached = symbolCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < 60000) { // 1 minute cache TTL\n return cached.symbols;\n }\n\n let symbols: SymbolSet;\n\n // If emoji/nerd font is explicitly disabled, use ASCII\n if (config.noEmoji) {\n symbols = { ...ASCII_SYMBOLS, ...config.symbols, ...config.asciiSymbols };\n } else {\n // Try to detect Nerd Font support\n const detection = await detectNerdFontSupport();\n\n if (detection.hasNerdFont) {\n // Merge user's custom symbols with Nerd Font defaults\n symbols = { ...NERD_FONT_SYMBOLS, ...config.symbols };\n } else {\n // Merge user's custom ASCII symbols with ASCII defaults\n symbols = { ...ASCII_SYMBOLS, ...config.asciiSymbols };\n }\n }\n\n // Cache the result with timestamp\n symbolCache.set(cacheKey, { symbols, timestamp: Date.now() });\n return symbols;\n}\n\n/**\n * Comprehensive Nerd Font support detection\n */\nasync function detectNerdFontSupport(): Promise<TerminalInfo> {\n const terminalInfo: TerminalInfo = {\n hasNerdFont: false,\n terminal: '',\n font: '',\n method: '',\n };\n\n // Method 1: Environment variable NERD_FONT=1\n if (process.env.NERD_FONT === '1') {\n terminalInfo.hasNerdFont = true;\n terminalInfo.method = 'NERD_FONT env var';\n return terminalInfo;\n }\n\n // Method 2: Terminal program detection\n const termProgram = process.env.TERM_PROGRAM;\n const term = process.env.TERM;\n\n if (termProgram) {\n terminalInfo.terminal = termProgram;\n terminalInfo.method = 'TERM_PROGRAM detection';\n\n // These terminals commonly have Nerd Font support\n const nerdFontTerminals = ['vscode', 'ghostty', 'wezterm', 'iterm'];\n if (nerdFontTerminals.includes(termProgram)) {\n terminalInfo.hasNerdFont = true;\n return terminalInfo;\n }\n }\n\n if (term) {\n terminalInfo.terminal = term;\n terminalInfo.method = 'TERM detection';\n\n // These terminal types commonly support Nerd Fonts\n const nerdFontTerms = ['alacritty', 'kitty', 'wezterm', 'ghostty', 'xterm-256color'];\n if (nerdFontTerms.includes(term)) {\n terminalInfo.hasNerdFont = true;\n return terminalInfo;\n }\n }\n\n // Method 3: Try to detect via font list (Unix/Linux/macOS)\n const fontDetection = await detectViaFontList();\n if (fontDetection.hasNerdFont) {\n terminalInfo.hasNerdFont = true;\n terminalInfo.font = fontDetection.font;\n terminalInfo.method = 'font list detection';\n return terminalInfo;\n }\n\n // Method 4: Check for common Nerd Font installation patterns\n const installDetection = await detectNerdFontInstallation();\n if (installDetection.hasNerdFont) {\n terminalInfo.hasNerdFont = true;\n terminalInfo.font = installDetection.font;\n terminalInfo.method = 'installation detection';\n return terminalInfo;\n }\n\n // Method 5: Check environment variables that might indicate font support\n const envDetection = detectFromEnvironment();\n if (envDetection.hasNerdFont) {\n terminalInfo.hasNerdFont = true;\n terminalInfo.method = 'environment detection';\n return terminalInfo;\n }\n\n // Method 6: Platform-specific detection\n const platformDetection = await detectPlatformSpecific();\n if (platformDetection.hasNerdFont) {\n terminalInfo.hasNerdFont = true;\n terminalInfo.method = 'platform-specific detection';\n return terminalInfo;\n }\n\n // Default: no Nerd Font detected\n return terminalInfo;\n}\n\n/**\n * Detect Nerd Font via font list command\n */\nasync function detectViaFontList(): Promise<{ hasNerdFont: boolean; font: string }> {\n try {\n const { exec } = await import('child_process');\n const { promisify } = await import('util');\n const execAsync = promisify(exec);\n\n // Try fc-list (Linux) or system_profiler (macOS)\n let fontListCommand = '';\n const platform = process.platform;\n\n if (platform === 'linux') {\n fontListCommand = 'fc-list';\n } else if (platform === 'darwin') {\n fontListCommand = 'system_profiler SPFontsDataType 2>/dev/null || system_profiler SPFontsDataType';\n }\n\n if (fontListCommand) {\n const { stdout } = await execAsync(fontListCommand, {\n timeout: 3000,\n encoding: 'utf-8',\n });\n\n const nerdFontPatterns = [\n /nerd font/i,\n /symbols only/i,\n /jetbrains mono.*nerd/i,\n /fira code.*nerd/i,\n /hack.*nerd/i,\n /source code pro.*nerd/i,\n /ubuntu mono.*nerd/i,\n /anonymous pro.*nerd/i,\n ];\n\n for (const pattern of nerdFontPatterns) {\n if (pattern.test(stdout)) {\n // Try to extract font name\n const match = stdout.match(/([^:\\n]*)(?=\\s*(nerd|symbols))/i);\n const fontName = match ? match[1] : 'Nerd Font';\n return { hasNerdFont: true, font: fontName?.trim() || 'Nerd Font' };\n }\n }\n }\n } catch {\n // Font detection failed\n }\n\n return { hasNerdFont: false, font: '' };\n}\n\n/**\n * Detect Nerd Font installation in common locations\n */\nasync function detectNerdFontInstallation(): Promise<{ hasNerdFont: boolean; font: string }> {\n try {\n const { access, readdir } = await import('fs/promises');\n const { homedir } = await import('os');\n\n const platform = process.platform;\n const fontPaths: string[] = [];\n\n if (platform === 'darwin') {\n fontPaths.push(\n `${homedir()}/Library/Fonts`,\n '/System/Library/Fonts',\n '/Library/Fonts'\n );\n } else if (platform === 'linux') {\n fontPaths.push(\n `${homedir()}/.local/share/fonts`,\n `${homedir()}/.fonts`,\n '/usr/share/fonts',\n '/usr/local/share/fonts'\n );\n }\n\n const nerdFontNames = [\n 'jetbrains-mono-nerd-font',\n 'fira-code-nerd-font',\n 'hack-nerd-font',\n 'source-code-pro-nerd-font',\n 'ubuntu-mono-nerd-font',\n 'anonymous-pro-nerd-font',\n ];\n\n for (const fontPath of fontPaths) {\n try {\n await access(fontPath);\n const files = await readdir(fontPath);\n\n for (const file of files) {\n const fileName = file.toLowerCase();\n for (const nerdFontName of nerdFontNames) {\n if (fileName.includes(nerdFontName)) {\n return { hasNerdFont: true, font: file };\n }\n }\n }\n } catch {\n // Can't access this font directory\n }\n }\n } catch {\n // Font detection failed\n }\n\n return { hasNerdFont: false, font: '' };\n}\n\n/**\n * Detect Nerd Font support from environment variables\n */\nfunction detectFromEnvironment(): { hasNerdFont: boolean } {\n // Check for various environment variables that might indicate Nerd Font support\n const nerdFontEnvVars = [\n 'POWERLINE_COMMAND', // Often used with Nerd Fonts\n 'NERDFONTS', // Some terminals set this\n 'FONT_FAMILY', // Some terminals expose the current font\n ];\n\n for (const envVar of nerdFontEnvVars) {\n const value = process.env[envVar];\n if (value && value.toLowerCase().includes('nerd')) {\n return { hasNerdFont: true };\n }\n }\n\n // Check if we're in a development environment that likely has Nerd Fonts\n if (\n process.env.VSCODE_PID ||\n process.env.TERM_PROGRAM === 'vscode' ||\n process.env.TERM_PROGRAM === 'ghostty' ||\n process.env.TERM_PROGRAM === 'wezterm'\n ) {\n return { hasNerdFont: true };\n }\n\n return { hasNerdFont: false };\n}\n\n/**\n * Platform-specific Nerd Font detection\n */\nasync function detectPlatformSpecific(): Promise<{ hasNerdFont: boolean }> {\n const platform = process.platform;\n\n if (platform === 'darwin') {\n // macOS: check if we're in a common development environment\n try {\n const { exec } = await import('child_process');\n const { promisify } = await import('util');\n const execAsync = promisify(exec);\n\n // Check for Homebrew-installed Nerd Fonts\n const { stdout } = await execAsync('brew list | grep -i font', {\n timeout: 2000,\n encoding: 'utf-8',\n });\n\n if (stdout.includes('nerd')) {\n return { hasNerdFont: true };\n }\n } catch {\n // brew command failed or not available\n }\n }\n\n return { hasNerdFont: false };\n}\n\n/**\n * Get environment symbols with version information\n */\nexport function getEnvironmentSymbols(symbolSet: SymbolSet): { [key: string]: string } {\n return {\n node: '', // Node.js\n python: '', // Python\n docker: '', // Docker\n git: symbolSet.git,\n model: symbolSet.model,\n };\n}\n\n/**\n * Test if symbols can be displayed properly\n */\nexport async function testSymbolDisplay(_symbols: SymbolSet): Promise<boolean> {\n // In a terminal environment, we can't easily test if symbols display correctly\n // For now, we'll assume that if we detected Nerd Font support, they can be displayed\n return true;\n}"]}
@@ -0,0 +1,29 @@
1
+ import { Config } from '../core/config.js';
2
+ /**
3
+ * Terminal width detection utilities
4
+ * Ported from bash implementation with cross-platform Node.js support
5
+ */
6
+ /**
7
+ * Get terminal width using multiple detection methods
8
+ * Ordered from most reliable to fallback methods
9
+ */
10
+ export declare function getTerminalWidth(config: Config): Promise<number>;
11
+ /**
12
+ * Debug width detection (matches bash implementation)
13
+ */
14
+ export declare function debugWidthDetection(config: Config): Promise<void>;
15
+ /**
16
+ * Text truncation utilities
17
+ */
18
+ /**
19
+ * Simple text truncation with ellipsis
20
+ */
21
+ export declare function truncateText(text: string, maxLength: number): string;
22
+ /**
23
+ * Smart truncation with branch prioritization (matches bash implementation)
24
+ */
25
+ export declare function smartTruncate(project: string, gitInfo: string, maxLen: number, _config: Config): string;
26
+ /**
27
+ * Soft wrapping function (experimental)
28
+ */
29
+ export declare function softWrapText(text: string, maxLength: number, wrapChar?: 'newline' | 'space', modelPrefix?: string): string;
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Terminal width detection utilities
3
+ * Ported from bash implementation with cross-platform Node.js support
4
+ */
5
+ /**
6
+ * Get terminal width using multiple detection methods
7
+ * Ordered from most reliable to fallback methods
8
+ */
9
+ export async function getTerminalWidth(config) {
10
+ // Method 0: Respect manual width override first (for testing)
11
+ if (config.forceWidth && config.forceWidth > 0) {
12
+ return config.forceWidth;
13
+ }
14
+ // Method 1: Try COLUMNS environment variable
15
+ const columnsEnv = process.env.COLUMNS;
16
+ if (columnsEnv) {
17
+ const columns = parseInt(columnsEnv, 10);
18
+ if (!isNaN(columns) && columns > 0) {
19
+ return columns;
20
+ }
21
+ }
22
+ // Method 2: Try Node.js process.stdout.columns
23
+ if (process.stdout.columns && process.stdout.columns > 0) {
24
+ return process.stdout.columns;
25
+ }
26
+ // Method 3: Try tput command (Unix/Linux/macOS)
27
+ const tputWidth = await tryCommand('tput', ['cols']);
28
+ if (tputWidth) {
29
+ return tputWidth;
30
+ }
31
+ // Method 4: Try stty command (Unix/Linux/macOS)
32
+ const sttyWidth = await tryStty();
33
+ if (sttyWidth) {
34
+ return sttyWidth;
35
+ }
36
+ // Method 5: Check Claude Code specific environment
37
+ const claudeWidth = process.env.CLAUDE_CODE_TERMINAL_WIDTH;
38
+ if (claudeWidth) {
39
+ const width = parseInt(claudeWidth, 10);
40
+ if (!isNaN(width) && width > 0) {
41
+ return width;
42
+ }
43
+ }
44
+ // Method 6: Terminal-specific defaults
45
+ const termProgram = process.env.TERM_PROGRAM;
46
+ const term = process.env.TERM;
47
+ if (termProgram === 'vscode' && process.env.VSCODE_PID) {
48
+ return 120; // VS Code default
49
+ }
50
+ if (['ghostty', 'wezterm', 'iterm'].includes(termProgram || '')) {
51
+ return 120; // Modern terminals default to wider
52
+ }
53
+ if (term && ['alacritty', 'kitty', 'wezterm', 'ghostty', 'xterm-256color'].includes(term)) {
54
+ return 120; // Modern terminals
55
+ }
56
+ // Method 7: Check for Windows Terminal
57
+ if (process.env.WT_SESSION || process.env.WT_PROFILE_ID) {
58
+ return 120; // Windows Terminal
59
+ }
60
+ // Final fallback: conservative 80-column default
61
+ return 80;
62
+ }
63
+ /**
64
+ * Execute a command and parse numeric output
65
+ */
66
+ async function tryCommand(command, args) {
67
+ try {
68
+ const { exec } = await import('child_process');
69
+ const { promisify } = await import('util');
70
+ const execAsync = promisify(exec);
71
+ const { stdout } = await execAsync(`${command} ${args.join(' ')}`, {
72
+ timeout: 1000, // 1 second timeout
73
+ encoding: 'utf-8',
74
+ });
75
+ const width = parseInt(stdout.trim(), 10);
76
+ if (!isNaN(width) && width > 0) {
77
+ return width;
78
+ }
79
+ }
80
+ catch {
81
+ // Command failed or not available
82
+ }
83
+ return null;
84
+ }
85
+ /**
86
+ * Try stty size command
87
+ */
88
+ async function tryStty() {
89
+ try {
90
+ const { exec } = await import('child_process');
91
+ const { promisify } = await import('util');
92
+ const execAsync = promisify(exec);
93
+ const { stdout } = await execAsync('stty size', {
94
+ timeout: 1000,
95
+ encoding: 'utf-8',
96
+ });
97
+ // stty size returns: "rows cols"
98
+ const parts = stdout.trim().split(' ');
99
+ if (parts.length === 2) {
100
+ const width = parseInt(parts[1] || '0', 10);
101
+ if (!isNaN(width) && width > 0) {
102
+ return width;
103
+ }
104
+ }
105
+ }
106
+ catch {
107
+ // stty failed or not available
108
+ }
109
+ return null;
110
+ }
111
+ /**
112
+ * Debug width detection (matches bash implementation)
113
+ */
114
+ export async function debugWidthDetection(config) {
115
+ if (!config.debugWidth) {
116
+ return;
117
+ }
118
+ console.error('[WIDTH DEBUG] Debug mode enabled');
119
+ console.error('[WIDTH DEBUG] Methods tried:');
120
+ // Test process.stdout.columns
121
+ if (process.stdout.columns) {
122
+ console.error(`[WIDTH DEBUG] process.stdout.columns: ${process.stdout.columns}`);
123
+ }
124
+ else {
125
+ console.error('[WIDTH DEBUG] process.stdout.columns: not available');
126
+ }
127
+ // Test COLUMNS variable
128
+ const columnsEnv = process.env.COLUMNS;
129
+ if (columnsEnv) {
130
+ console.error(`[WIDTH DEBUG] COLUMNS variable: ${columnsEnv}`);
131
+ }
132
+ else {
133
+ console.error('[WIDTH DEBUG] COLUMNS variable: not set');
134
+ }
135
+ // Test tput
136
+ const tputWidth = await tryCommand('tput', ['cols']);
137
+ if (tputWidth) {
138
+ console.error(`[WIDTH DEBUG] tput cols: ${tputWidth}`);
139
+ }
140
+ else {
141
+ console.error('[WIDTH DEBUG] tput: not available or failed');
142
+ }
143
+ // Test stty
144
+ const sttyWidth = await tryStty();
145
+ if (sttyWidth) {
146
+ console.error(`[WIDTH DEBUG] stty size: ${sttyWidth}`);
147
+ }
148
+ else {
149
+ console.error('[WIDTH DEBUG] stty: not available or failed');
150
+ }
151
+ // Test environment variables
152
+ console.error(`[WIDTH DEBUG] CLAUDE_CODE_STATUSLINE_FORCE_WIDTH: ${config.forceWidth || 'not set'}`);
153
+ console.error(`[WIDTH DEBUG] COLUMNS variable: ${columnsEnv || 'not set'}`);
154
+ console.error(`[WIDTH DEBUG] CLAUDE_CODE_TERMINAL_WIDTH: ${process.env.CLAUDE_CODE_TERMINAL_WIDTH || 'not set'}`);
155
+ console.error(`[WIDTH DEBUG] TERM_PROGRAM: ${process.env.TERM_PROGRAM || 'not set'}`);
156
+ console.error(`[WIDTH DEBUG] TERM: ${process.env.TERM || 'not set'}`);
157
+ // Show final result
158
+ const finalWidth = await getTerminalWidth(config);
159
+ console.error(`[WIDTH DEBUG] Final detected width: ${finalWidth}`);
160
+ console.error(`[WIDTH DEBUG] Statusline will use: ${finalWidth - config.rightMargin} columns max`);
161
+ }
162
+ /**
163
+ * Text truncation utilities
164
+ */
165
+ /**
166
+ * Simple text truncation with ellipsis
167
+ */
168
+ export function truncateText(text, maxLength) {
169
+ if (text.length <= maxLength) {
170
+ return text;
171
+ }
172
+ // Edge case: if maxLength is too small, return minimal result
173
+ if (maxLength < 4) {
174
+ return '..';
175
+ }
176
+ return `${text.substring(0, maxLength - 2)}..`;
177
+ }
178
+ /**
179
+ * Smart truncation with branch prioritization (matches bash implementation)
180
+ */
181
+ export function smartTruncate(project, gitInfo, maxLen, _config) {
182
+ // Step 1: Check if everything fits
183
+ if (project.length + gitInfo.length <= maxLen) {
184
+ return '';
185
+ }
186
+ // Step 2: Truncate project only (preserve branch)
187
+ const projLen = maxLen - gitInfo.length - 2;
188
+ if (projLen >= 5) {
189
+ return `${project.substring(0, projLen)}..${gitInfo}`;
190
+ }
191
+ // Step 3: Truncate project + branch (preserve indicators)
192
+ let indicators = '';
193
+ const bracketMatch = gitInfo.match(/\[([^\]]+)\]/);
194
+ if (bracketMatch) {
195
+ indicators = bracketMatch[1] || '';
196
+ }
197
+ const branchLen = maxLen - indicators.length - 8;
198
+ if (branchLen >= 8) {
199
+ const gitPrefix = gitInfo.substring(0, Math.min(gitInfo.length, branchLen));
200
+ return `${project.substring(0, 4)}..${gitPrefix}..${indicators ? ` [${indicators}]` : ''}`;
201
+ }
202
+ // Step 4: Basic fallback
203
+ return `${project.substring(0, maxLen)}..`;
204
+ }
205
+ /**
206
+ * Soft wrapping function (experimental)
207
+ */
208
+ export function softWrapText(text, maxLength, wrapChar = 'newline', modelPrefix = '*') {
209
+ if (text.length <= maxLength) {
210
+ return text;
211
+ }
212
+ if (wrapChar === 'newline') {
213
+ // Smart wrap: try to break at space or safe character
214
+ let breakPos = maxLength;
215
+ let foundBreak = false;
216
+ // Look for safe break points (spaces)
217
+ for (let i = Math.min(maxLength - 1, text.length - 1); i > Math.max(maxLength - 20, 0) && i >= 0; i--) {
218
+ const char = text[i];
219
+ if (char === ' ') {
220
+ breakPos = i;
221
+ foundBreak = true;
222
+ break;
223
+ }
224
+ }
225
+ // If no safe break found and we're very close to max_length, just fit without wrapping
226
+ if (!foundBreak && maxLength - text.length > -3) {
227
+ return text;
228
+ }
229
+ // Adjust break position to avoid splitting multi-byte UTF-8 characters
230
+ // UTF-8 continuation bytes have their two most significant bits set to 10 (0x80 to 0xBF)
231
+ while (breakPos > 0 && (text.charCodeAt(breakPos) & 0xC0) === 0x80) {
232
+ breakPos--;
233
+ }
234
+ const firstLine = text.substring(0, breakPos);
235
+ let secondLine = text.substring(breakPos);
236
+ // Remove leading space from second line if we broke at space
237
+ if (secondLine.startsWith(' ')) {
238
+ secondLine = secondLine.substring(1);
239
+ }
240
+ // Only wrap if second line has meaningful content
241
+ if (secondLine) {
242
+ return `${firstLine}\n${modelPrefix}${secondLine}`;
243
+ }
244
+ else {
245
+ return firstLine;
246
+ }
247
+ }
248
+ else {
249
+ // Use space separator for wrapping
250
+ let breakPos = maxLength;
251
+ // Adjust break position to avoid splitting multi-byte UTF-8 characters
252
+ // UTF-8 continuation bytes have their two most significant bits set to 10 (0x80 to 0xBF)
253
+ while (breakPos > 0 && (text.charCodeAt(breakPos) & 0xC0) === 0x80) {
254
+ breakPos--;
255
+ }
256
+ const firstLine = text.substring(0, breakPos);
257
+ const secondLine = text.substring(breakPos);
258
+ return `${firstLine} ${secondLine}`;
259
+ }
260
+ }
261
+ //# sourceMappingURL=width.js.map