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.
- package/LICENSE +203 -0
- package/README.md +362 -0
- package/bin/claude-statusline +22 -0
- package/dist/core/cache.d.ts +67 -0
- package/dist/core/cache.js +223 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/config.d.ts +190 -0
- package/dist/core/config.js +192 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/security.d.ts +27 -0
- package/dist/core/security.js +154 -0
- package/dist/core/security.js.map +1 -0
- package/dist/env/context.d.ts +92 -0
- package/dist/env/context.js +295 -0
- package/dist/env/context.js.map +1 -0
- package/dist/git/native.d.ts +35 -0
- package/dist/git/native.js +141 -0
- package/dist/git/native.js.map +1 -0
- package/dist/git/status.d.ts +65 -0
- package/dist/git/status.js +256 -0
- package/dist/git/status.js.map +1 -0
- package/dist/index.bundle.js +11 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +396 -0
- package/dist/index.js.map +1 -0
- package/dist/metafile.prod.json +473 -0
- package/dist/ui/symbols.d.ts +31 -0
- package/dist/ui/symbols.js +308 -0
- package/dist/ui/symbols.js.map +1 -0
- package/dist/ui/width.d.ts +29 -0
- package/dist/ui/width.js +261 -0
- package/dist/ui/width.js.map +1 -0
- package/dist/utils/runtime.d.ts +31 -0
- package/dist/utils/runtime.js +82 -0
- package/dist/utils/runtime.js.map +1 -0
- package/docs/ARCHITECTURE.md +336 -0
- package/docs/FEATURE_COMPARISON.md +178 -0
- package/docs/MIGRATION.md +354 -0
- package/docs/README.md +101 -0
- package/docs/eval-01-terminal-widths.md +519 -0
- package/docs/guide-01-configuration.md +277 -0
- package/docs/guide-02-troubleshooting.md +416 -0
- package/docs/guide-03-performance.md +183 -0
- package/docs/prd-01-typescript-perf-optimization.md +480 -0
- package/docs/research-01-sandbox-detection.md +169 -0
- package/docs/research-02-competitive-analysis.md +226 -0
- package/docs/research-03-platform-analysis.md +142 -0
- package/package.json +89 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Execute a git command using child_process.spawn
|
|
4
|
+
* Provides native git execution without external dependencies
|
|
5
|
+
*/
|
|
6
|
+
export async function executeGitCommand(args, options = {}) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const git = spawn('git', args, {
|
|
9
|
+
cwd: options.cwd || process.cwd(),
|
|
10
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
11
|
+
timeout: options.timeout || 5000,
|
|
12
|
+
});
|
|
13
|
+
let stdout = '';
|
|
14
|
+
let stderr = '';
|
|
15
|
+
git.stdout.on('data', (data) => {
|
|
16
|
+
stdout += data.toString();
|
|
17
|
+
});
|
|
18
|
+
git.stderr.on('data', (data) => {
|
|
19
|
+
stderr += data.toString();
|
|
20
|
+
});
|
|
21
|
+
git.on('close', (code) => {
|
|
22
|
+
if (code === 0) {
|
|
23
|
+
resolve(stdout);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
reject(new Error(`Git command failed with code ${code}: ${stderr || stdout}`));
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
git.on('error', (error) => {
|
|
30
|
+
reject(new Error(`Failed to execute git command: ${error.message}`));
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if directory is a git repository
|
|
36
|
+
*/
|
|
37
|
+
export async function checkIsRepo(cwd) {
|
|
38
|
+
try {
|
|
39
|
+
const options = cwd ? { cwd } : {};
|
|
40
|
+
await executeGitCommand(['rev-parse', '--git-dir'], options);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get current branch name using multiple fallback methods
|
|
49
|
+
*/
|
|
50
|
+
export async function getCurrentBranch(cwd) {
|
|
51
|
+
const options = cwd ? { cwd } : {};
|
|
52
|
+
// Method 1: git branch --show-current (most reliable)
|
|
53
|
+
try {
|
|
54
|
+
const result = await executeGitCommand(['branch', '--show-current'], options);
|
|
55
|
+
const branch = result.trim();
|
|
56
|
+
if (branch) {
|
|
57
|
+
return branch;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Continue to next method
|
|
62
|
+
}
|
|
63
|
+
// Method 2: git rev-parse --abbrev-ref HEAD
|
|
64
|
+
try {
|
|
65
|
+
const result = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], options);
|
|
66
|
+
const branch = result.trim();
|
|
67
|
+
// Filter out HEAD if not on a branch
|
|
68
|
+
if (branch && branch !== 'HEAD') {
|
|
69
|
+
return branch;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Continue to next method
|
|
74
|
+
}
|
|
75
|
+
// Method 3: Parse git branch output for current branch
|
|
76
|
+
try {
|
|
77
|
+
const result = await executeGitCommand(['branch', '--no-color'], options);
|
|
78
|
+
const lines = result.split('\n');
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
if (line.startsWith('* ')) {
|
|
81
|
+
return line.substring(2).trim();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// All methods failed
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get git status in porcelain format
|
|
92
|
+
*/
|
|
93
|
+
export async function getPorcelainStatus(cwd) {
|
|
94
|
+
const options = cwd ? { cwd } : {};
|
|
95
|
+
return executeGitCommand(['status', '--porcelain'], options);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get stash list
|
|
99
|
+
*/
|
|
100
|
+
export async function getStashList(cwd) {
|
|
101
|
+
try {
|
|
102
|
+
const options = cwd ? { cwd } : {};
|
|
103
|
+
return executeGitCommand(['stash', 'list'], options);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return '';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get upstream branch reference
|
|
111
|
+
*/
|
|
112
|
+
export async function getUpstreamRef(cwd) {
|
|
113
|
+
try {
|
|
114
|
+
const options = cwd ? { cwd } : {};
|
|
115
|
+
const result = await executeGitCommand(['rev-parse', '--abbrev-ref', '@{u}'], options);
|
|
116
|
+
return result.trim();
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return '';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get ahead/behind counts
|
|
124
|
+
*/
|
|
125
|
+
export async function getAheadBehind(cwd) {
|
|
126
|
+
try {
|
|
127
|
+
const options = cwd ? { cwd } : {};
|
|
128
|
+
const result = await executeGitCommand(['rev-list', '--count', '--left-right', '@{u}...HEAD'], options);
|
|
129
|
+
const counts = result.trim().split('\t');
|
|
130
|
+
if (counts.length === 2) {
|
|
131
|
+
const behind = parseInt(counts[0] || '0', 10);
|
|
132
|
+
const ahead = parseInt(counts[1] || '0', 10);
|
|
133
|
+
return { ahead, behind };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// No upstream or other error
|
|
138
|
+
}
|
|
139
|
+
return { ahead: 0, behind: 0 };
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native.js","sourceRoot":"","sources":["../../src/git/native.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'child_process';\n\n/**\n * Execute a git command using child_process.spawn\n * Provides native git execution without external dependencies\n */\nexport async function executeGitCommand(\n args: string[],\n options: {\n cwd?: string;\n timeout?: number;\n } = {}\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const git = spawn('git', args, {\n cwd: options.cwd || process.cwd(),\n stdio: ['ignore', 'pipe', 'pipe'],\n timeout: options.timeout || 5000,\n });\n\n let stdout = '';\n let stderr = '';\n\n git.stdout.on('data', (data) => {\n stdout += data.toString();\n });\n\n git.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n git.on('close', (code) => {\n if (code === 0) {\n resolve(stdout);\n } else {\n reject(new Error(`Git command failed with code ${code}: ${stderr || stdout}`));\n }\n });\n\n git.on('error', (error) => {\n reject(new Error(`Failed to execute git command: ${error.message}`));\n });\n });\n}\n\n/**\n * Check if directory is a git repository\n */\nexport async function checkIsRepo(cwd?: string): Promise<boolean> {\n try {\n const options = cwd ? { cwd } : {};\n await executeGitCommand(['rev-parse', '--git-dir'], options);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get current branch name using multiple fallback methods\n */\nexport async function getCurrentBranch(cwd?: string): Promise<string | null> {\n const options = cwd ? { cwd } : {};\n\n // Method 1: git branch --show-current (most reliable)\n try {\n const result = await executeGitCommand(['branch', '--show-current'], options);\n const branch = result.trim();\n if (branch) {\n return branch;\n }\n } catch {\n // Continue to next method\n }\n\n // Method 2: git rev-parse --abbrev-ref HEAD\n try {\n const result = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], options);\n const branch = result.trim();\n // Filter out HEAD if not on a branch\n if (branch && branch !== 'HEAD') {\n return branch;\n }\n } catch {\n // Continue to next method\n }\n\n // Method 3: Parse git branch output for current branch\n try {\n const result = await executeGitCommand(['branch', '--no-color'], options);\n const lines = result.split('\\n');\n for (const line of lines) {\n if (line.startsWith('* ')) {\n return line.substring(2).trim();\n }\n }\n } catch {\n // All methods failed\n }\n\n return null;\n}\n\n/**\n * Get git status in porcelain format\n */\nexport async function getPorcelainStatus(cwd?: string): Promise<string> {\n const options = cwd ? { cwd } : {};\n return executeGitCommand(['status', '--porcelain'], options);\n}\n\n/**\n * Get stash list\n */\nexport async function getStashList(cwd?: string): Promise<string> {\n try {\n const options = cwd ? { cwd } : {};\n return executeGitCommand(['stash', 'list'], options);\n } catch {\n return '';\n }\n}\n\n/**\n * Get upstream branch reference\n */\nexport async function getUpstreamRef(cwd?: string): Promise<string> {\n try {\n const options = cwd ? { cwd } : {};\n const result = await executeGitCommand(['rev-parse', '--abbrev-ref', '@{u}'], options);\n return result.trim();\n } catch {\n return '';\n }\n}\n\n/**\n * Get ahead/behind counts\n */\nexport async function getAheadBehind(cwd?: string): Promise<{ ahead: number; behind: number }> {\n try {\n const options = cwd ? { cwd } : {};\n const result = await executeGitCommand(['rev-list', '--count', '--left-right', '@{u}...HEAD'], options);\n const counts = result.trim().split('\\t');\n\n if (counts.length === 2) {\n const behind = parseInt(counts[0] || '0', 10);\n const ahead = parseInt(counts[1] || '0', 10);\n return { ahead, behind };\n }\n } catch {\n // No upstream or other error\n }\n\n return { ahead: 0, behind: 0 };\n}"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Config } from '../core/config.js';
|
|
2
|
+
import { Cache } from '../core/cache.js';
|
|
3
|
+
/**
|
|
4
|
+
* Git status information interface
|
|
5
|
+
*/
|
|
6
|
+
export interface GitInfo {
|
|
7
|
+
branch: string;
|
|
8
|
+
indicators: GitIndicators;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Git status indicators
|
|
12
|
+
*/
|
|
13
|
+
export interface GitIndicators {
|
|
14
|
+
stashed: number;
|
|
15
|
+
staged: number;
|
|
16
|
+
modified: number;
|
|
17
|
+
untracked: number;
|
|
18
|
+
renamed: number;
|
|
19
|
+
deleted: number;
|
|
20
|
+
conflicts: number;
|
|
21
|
+
ahead: number;
|
|
22
|
+
behind: number;
|
|
23
|
+
diverged: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Empty git indicators (no changes)
|
|
27
|
+
*/
|
|
28
|
+
export declare const EMPTY_INDICATORS: GitIndicators;
|
|
29
|
+
/**
|
|
30
|
+
* Git operations and status parsing
|
|
31
|
+
* Ported from bash implementation with enhanced TypeScript safety
|
|
32
|
+
*/
|
|
33
|
+
export declare class GitOperations {
|
|
34
|
+
private config;
|
|
35
|
+
private cache;
|
|
36
|
+
constructor(config: Config, cache: Cache);
|
|
37
|
+
/**
|
|
38
|
+
* Get git information for a directory
|
|
39
|
+
*/
|
|
40
|
+
getGitInfo(directory: string): Promise<GitInfo | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Get current branch name with caching
|
|
43
|
+
*/
|
|
44
|
+
private getCurrentBranch;
|
|
45
|
+
/**
|
|
46
|
+
* Get comprehensive git status indicators
|
|
47
|
+
*/
|
|
48
|
+
private getGitIndicators;
|
|
49
|
+
/**
|
|
50
|
+
* Get number of stashed changes
|
|
51
|
+
*/
|
|
52
|
+
private getStashedCount;
|
|
53
|
+
/**
|
|
54
|
+
* Get ahead/behind count for tracking branch
|
|
55
|
+
*/
|
|
56
|
+
private getAheadBehind;
|
|
57
|
+
/**
|
|
58
|
+
* Format git indicators into display string
|
|
59
|
+
*/
|
|
60
|
+
formatIndicators(indicators: GitIndicators, symbols: Config['symbols']): string;
|
|
61
|
+
/**
|
|
62
|
+
* Get git status string for display
|
|
63
|
+
*/
|
|
64
|
+
formatGitStatus(gitInfo: GitInfo, symbols: Config['symbols']): string;
|
|
65
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { CacheKeys } from '../core/cache.js';
|
|
2
|
+
import { checkIsRepo, getCurrentBranch, getPorcelainStatus, getStashList, getUpstreamRef, getAheadBehind, } from './native.js';
|
|
3
|
+
/**
|
|
4
|
+
* Empty git indicators (no changes)
|
|
5
|
+
*/
|
|
6
|
+
export const EMPTY_INDICATORS = {
|
|
7
|
+
stashed: 0,
|
|
8
|
+
staged: 0,
|
|
9
|
+
modified: 0,
|
|
10
|
+
untracked: 0,
|
|
11
|
+
renamed: 0,
|
|
12
|
+
deleted: 0,
|
|
13
|
+
conflicts: 0,
|
|
14
|
+
ahead: 0,
|
|
15
|
+
behind: 0,
|
|
16
|
+
diverged: false,
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Git operations and status parsing
|
|
20
|
+
* Ported from bash implementation with enhanced TypeScript safety
|
|
21
|
+
*/
|
|
22
|
+
export class GitOperations {
|
|
23
|
+
config;
|
|
24
|
+
cache;
|
|
25
|
+
constructor(config, cache) {
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.cache = cache;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get git information for a directory
|
|
31
|
+
*/
|
|
32
|
+
async getGitInfo(directory) {
|
|
33
|
+
if (this.config.noGitStatus) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
// Check if this is a git repository
|
|
38
|
+
const isRepo = await checkIsRepo(directory);
|
|
39
|
+
if (!isRepo) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// Get current branch
|
|
43
|
+
const branch = await this.getCurrentBranch(directory);
|
|
44
|
+
if (!branch) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
// Get status indicators
|
|
48
|
+
const indicators = await this.getGitIndicators(directory);
|
|
49
|
+
return { branch, indicators };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.debug('[DEBUG] Git operation failed:', error instanceof Error ? error.message : String(error));
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get current branch name with caching
|
|
58
|
+
*/
|
|
59
|
+
async getCurrentBranch(directory) {
|
|
60
|
+
const cacheKey = `${CacheKeys.GIT_BRANCH(directory)}_current`;
|
|
61
|
+
// Try cache first
|
|
62
|
+
const cached = await this.cache.get(cacheKey, 60); // 1 minute TTL for branch
|
|
63
|
+
if (cached) {
|
|
64
|
+
return cached;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const branch = await getCurrentBranch(directory);
|
|
68
|
+
if (branch) {
|
|
69
|
+
await this.cache.set(cacheKey, branch);
|
|
70
|
+
return branch;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.debug('[DEBUG] Failed to get current branch:', error instanceof Error ? error.message : String(error));
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get comprehensive git status indicators
|
|
81
|
+
*/
|
|
82
|
+
async getGitIndicators(directory) {
|
|
83
|
+
const indicators = { ...EMPTY_INDICATORS };
|
|
84
|
+
try {
|
|
85
|
+
// Get porcelain status for parsing
|
|
86
|
+
const statusResult = await getPorcelainStatus(directory);
|
|
87
|
+
const statusLines = statusResult.split('\n')
|
|
88
|
+
.map(line => line.trimEnd()) // Only trim trailing whitespace, not leading!
|
|
89
|
+
.filter(line => line.length > 0);
|
|
90
|
+
// Parse each status line
|
|
91
|
+
for (const line of statusLines) {
|
|
92
|
+
if (line.length < 2)
|
|
93
|
+
continue;
|
|
94
|
+
const stagedChar = line.charAt(0);
|
|
95
|
+
const unstagedChar = line.charAt(1);
|
|
96
|
+
// Check for conflicts (U = unmerged)
|
|
97
|
+
if (stagedChar === 'U' || unstagedChar === 'U' ||
|
|
98
|
+
(stagedChar === 'A' && unstagedChar === 'A') ||
|
|
99
|
+
(stagedChar === 'D' && unstagedChar === 'D')) {
|
|
100
|
+
indicators.conflicts++;
|
|
101
|
+
}
|
|
102
|
+
// Check for untracked files
|
|
103
|
+
else if (stagedChar === '?' && unstagedChar === '?') {
|
|
104
|
+
indicators.untracked++;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Parse staged changes (first character)
|
|
108
|
+
switch (stagedChar) {
|
|
109
|
+
case 'M':
|
|
110
|
+
indicators.staged++; // Modified
|
|
111
|
+
break;
|
|
112
|
+
case 'A':
|
|
113
|
+
indicators.staged++; // Added
|
|
114
|
+
break;
|
|
115
|
+
case 'D':
|
|
116
|
+
indicators.deleted++; // Deleted (staged)
|
|
117
|
+
break;
|
|
118
|
+
case 'R':
|
|
119
|
+
indicators.renamed++; // Renamed (staged)
|
|
120
|
+
break;
|
|
121
|
+
case 'C':
|
|
122
|
+
indicators.staged++; // Copied (staged)
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
// Parse unstaged changes (second character)
|
|
126
|
+
switch (unstagedChar) {
|
|
127
|
+
case 'M':
|
|
128
|
+
indicators.modified++; // Modified
|
|
129
|
+
break;
|
|
130
|
+
case 'D':
|
|
131
|
+
indicators.deleted++; // Deleted (unstaged)
|
|
132
|
+
break;
|
|
133
|
+
case 'R':
|
|
134
|
+
indicators.renamed++; // Renamed (unstaged)
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Get stashed changes count
|
|
140
|
+
indicators.stashed = await this.getStashedCount(directory);
|
|
141
|
+
// Get ahead/behind information
|
|
142
|
+
const { ahead, behind } = await this.getAheadBehind(directory);
|
|
143
|
+
indicators.ahead = ahead;
|
|
144
|
+
indicators.behind = behind;
|
|
145
|
+
indicators.diverged = ahead > 0 && behind > 0;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.debug('[DEBUG] Failed to parse git status:', error instanceof Error ? error.message : String(error));
|
|
149
|
+
}
|
|
150
|
+
return indicators;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get number of stashed changes
|
|
154
|
+
*/
|
|
155
|
+
async getStashedCount(directory) {
|
|
156
|
+
try {
|
|
157
|
+
const stashList = await getStashList(directory);
|
|
158
|
+
return stashList.trim().split('\n').filter(line => line.trim().length > 0).length;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get ahead/behind count for tracking branch
|
|
166
|
+
*/
|
|
167
|
+
async getAheadBehind(directory) {
|
|
168
|
+
try {
|
|
169
|
+
// Check if we have an upstream branch
|
|
170
|
+
const upstream = await getUpstreamRef(directory);
|
|
171
|
+
if (!upstream) {
|
|
172
|
+
return { ahead: 0, behind: 0 };
|
|
173
|
+
}
|
|
174
|
+
// Get ahead/behind count
|
|
175
|
+
return await getAheadBehind(directory);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// No upstream or other error
|
|
179
|
+
}
|
|
180
|
+
return { ahead: 0, behind: 0 };
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Format git indicators into display string
|
|
184
|
+
*/
|
|
185
|
+
formatIndicators(indicators, symbols) {
|
|
186
|
+
const indicatorChars = [];
|
|
187
|
+
// Custom order matching user preference: ⚑»!+?✘×⇕⇡⇣
|
|
188
|
+
// IMPORTANT: Do NOT change this order without updating documentation
|
|
189
|
+
const expectedOrder = ['stashed', 'renamed', 'modified', 'staged', 'untracked', 'deleted', 'conflicts'];
|
|
190
|
+
// Debug logging for order validation (only in development)
|
|
191
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
192
|
+
const actualOrder = [];
|
|
193
|
+
if (indicators.stashed > 0)
|
|
194
|
+
actualOrder.push('stashed');
|
|
195
|
+
if (indicators.renamed > 0)
|
|
196
|
+
actualOrder.push('renamed');
|
|
197
|
+
if (indicators.modified > 0)
|
|
198
|
+
actualOrder.push('modified');
|
|
199
|
+
if (indicators.staged > 0)
|
|
200
|
+
actualOrder.push('staged');
|
|
201
|
+
if (indicators.untracked > 0)
|
|
202
|
+
actualOrder.push('untracked');
|
|
203
|
+
if (indicators.deleted > 0)
|
|
204
|
+
actualOrder.push('deleted');
|
|
205
|
+
if (indicators.conflicts > 0)
|
|
206
|
+
actualOrder.push('conflicts');
|
|
207
|
+
// Validate order consistency
|
|
208
|
+
for (let i = 0; i < actualOrder.length - 1; i++) {
|
|
209
|
+
const currentIndex = expectedOrder.indexOf(actualOrder[i]);
|
|
210
|
+
const nextIndex = expectedOrder.indexOf(actualOrder[i + 1]);
|
|
211
|
+
if (currentIndex !== -1 && nextIndex !== -1 && currentIndex > nextIndex) {
|
|
212
|
+
console.warn(`[WARN] Indicator order violation: ${actualOrder[i]} should not come before ${actualOrder[i + 1]}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (indicators.stashed > 0)
|
|
217
|
+
indicatorChars.push(symbols.stashed);
|
|
218
|
+
if (indicators.renamed > 0)
|
|
219
|
+
indicatorChars.push(symbols.renamed);
|
|
220
|
+
if (indicators.modified > 0)
|
|
221
|
+
indicatorChars.push('!');
|
|
222
|
+
if (indicators.staged > 0)
|
|
223
|
+
indicatorChars.push(symbols.staged);
|
|
224
|
+
if (indicators.untracked > 0)
|
|
225
|
+
indicatorChars.push('?');
|
|
226
|
+
if (indicators.deleted > 0)
|
|
227
|
+
indicatorChars.push(symbols.deleted);
|
|
228
|
+
if (indicators.conflicts > 0)
|
|
229
|
+
indicatorChars.push(symbols.conflict);
|
|
230
|
+
// Ahead/behind status
|
|
231
|
+
if (indicators.diverged) {
|
|
232
|
+
indicatorChars.push(symbols.diverged);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
if (indicators.ahead > 0)
|
|
236
|
+
indicatorChars.push(symbols.ahead);
|
|
237
|
+
if (indicators.behind > 0)
|
|
238
|
+
indicatorChars.push(symbols.behind);
|
|
239
|
+
}
|
|
240
|
+
return indicatorChars.join('');
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get git status string for display
|
|
244
|
+
*/
|
|
245
|
+
formatGitStatus(gitInfo, symbols) {
|
|
246
|
+
const indicators = this.formatIndicators(gitInfo.indicators, symbols);
|
|
247
|
+
const gitSymbol = this.config.noEmoji ? symbols.git : symbols.git;
|
|
248
|
+
if (indicators) {
|
|
249
|
+
return ` ${gitSymbol} ${gitInfo.branch} [${indicators}]`;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
return ` ${gitSymbol} ${gitInfo.branch}`;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/git/status.ts"],"names":[],"mappings":"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","sourcesContent":["import { Config } from '../core/config.js';\nimport { Cache, CacheKeys } from '../core/cache.js';\nimport {\n checkIsRepo,\n getCurrentBranch,\n getPorcelainStatus,\n getStashList,\n getUpstreamRef,\n getAheadBehind,\n} from './native.js';\n\n/**\n * Git status information interface\n */\nexport interface GitInfo {\n branch: string;\n indicators: GitIndicators;\n}\n\n/**\n * Git status indicators\n */\nexport interface GitIndicators {\n stashed: number;\n staged: number;\n modified: number;\n untracked: number;\n renamed: number;\n deleted: number;\n conflicts: number;\n ahead: number;\n behind: number;\n diverged: boolean;\n}\n\n/**\n * Empty git indicators (no changes)\n */\nexport const EMPTY_INDICATORS: GitIndicators = {\n stashed: 0,\n staged: 0,\n modified: 0,\n untracked: 0,\n renamed: 0,\n deleted: 0,\n conflicts: 0,\n ahead: 0,\n behind: 0,\n diverged: false,\n};\n\n/**\n * Git operations and status parsing\n * Ported from bash implementation with enhanced TypeScript safety\n */\nexport class GitOperations {\n private config: Config;\n private cache: Cache;\n\n constructor(config: Config, cache: Cache) {\n this.config = config;\n this.cache = cache;\n }\n\n /**\n * Get git information for a directory\n */\n async getGitInfo(directory: string): Promise<GitInfo | null> {\n if (this.config.noGitStatus) {\n return null;\n }\n\n try {\n // Check if this is a git repository\n const isRepo = await checkIsRepo(directory);\n if (!isRepo) {\n return null;\n }\n\n // Get current branch\n const branch = await this.getCurrentBranch(directory);\n if (!branch) {\n return null;\n }\n\n // Get status indicators\n const indicators = await this.getGitIndicators(directory);\n\n return { branch, indicators };\n\n } catch (error) {\n console.debug('[DEBUG] Git operation failed:', error instanceof Error ? error.message : String(error));\n return null;\n }\n }\n\n /**\n * Get current branch name with caching\n */\n private async getCurrentBranch(directory: string): Promise<string | null> {\n const cacheKey = `${CacheKeys.GIT_BRANCH(directory)}_current`;\n\n // Try cache first\n const cached = await this.cache.get<string>(cacheKey, 60); // 1 minute TTL for branch\n if (cached) {\n return cached;\n }\n\n try {\n const branch = await getCurrentBranch(directory);\n\n if (branch) {\n await this.cache.set(cacheKey, branch);\n return branch;\n }\n\n return null;\n\n } catch (error) {\n console.debug('[DEBUG] Failed to get current branch:', error instanceof Error ? error.message : String(error));\n return null;\n }\n }\n\n /**\n * Get comprehensive git status indicators\n */\n private async getGitIndicators(directory: string): Promise<GitIndicators> {\n const indicators = { ...EMPTY_INDICATORS };\n\n try {\n // Get porcelain status for parsing\n const statusResult = await getPorcelainStatus(directory);\n const statusLines = statusResult.split('\\n')\n .map(line => line.trimEnd()) // Only trim trailing whitespace, not leading!\n .filter(line => line.length > 0);\n\n // Parse each status line\n for (const line of statusLines) {\n if (line.length < 2) continue;\n\n const stagedChar = line.charAt(0);\n const unstagedChar = line.charAt(1);\n\n // Check for conflicts (U = unmerged)\n if (stagedChar === 'U' || unstagedChar === 'U' ||\n (stagedChar === 'A' && unstagedChar === 'A') ||\n (stagedChar === 'D' && unstagedChar === 'D')) {\n indicators.conflicts++;\n }\n // Check for untracked files\n else if (stagedChar === '?' && unstagedChar === '?') {\n indicators.untracked++;\n } else {\n // Parse staged changes (first character)\n switch (stagedChar) {\n case 'M':\n indicators.staged++; // Modified\n break;\n case 'A':\n indicators.staged++; // Added\n break;\n case 'D':\n indicators.deleted++; // Deleted (staged)\n break;\n case 'R':\n indicators.renamed++; // Renamed (staged)\n break;\n case 'C':\n indicators.staged++; // Copied (staged)\n break;\n }\n\n // Parse unstaged changes (second character)\n switch (unstagedChar) {\n case 'M':\n indicators.modified++; // Modified\n break;\n case 'D':\n indicators.deleted++; // Deleted (unstaged)\n break;\n case 'R':\n indicators.renamed++; // Renamed (unstaged)\n break;\n }\n }\n }\n\n // Get stashed changes count\n indicators.stashed = await this.getStashedCount(directory);\n\n // Get ahead/behind information\n const { ahead, behind } = await this.getAheadBehind(directory);\n indicators.ahead = ahead;\n indicators.behind = behind;\n indicators.diverged = ahead > 0 && behind > 0;\n\n } catch (error) {\n console.debug('[DEBUG] Failed to parse git status:', error instanceof Error ? error.message : String(error));\n }\n\n return indicators;\n }\n\n /**\n * Get number of stashed changes\n */\n private async getStashedCount(directory: string): Promise<number> {\n try {\n const stashList = await getStashList(directory);\n return stashList.trim().split('\\n').filter(line => line.trim().length > 0).length;\n } catch {\n return 0;\n }\n }\n\n /**\n * Get ahead/behind count for tracking branch\n */\n private async getAheadBehind(directory: string): Promise<{ ahead: number; behind: number }> {\n try {\n // Check if we have an upstream branch\n const upstream = await getUpstreamRef(directory);\n if (!upstream) {\n return { ahead: 0, behind: 0 };\n }\n\n // Get ahead/behind count\n return await getAheadBehind(directory);\n\n } catch {\n // No upstream or other error\n }\n\n return { ahead: 0, behind: 0 };\n }\n\n /**\n * Format git indicators into display string\n */\n formatIndicators(indicators: GitIndicators, symbols: Config['symbols']): string {\n const indicatorChars: string[] = [];\n\n // Custom order matching user preference: ⚑»!+?✘×⇕⇡⇣\n // IMPORTANT: Do NOT change this order without updating documentation\n const expectedOrder = ['stashed', 'renamed', 'modified', 'staged', 'untracked', 'deleted', 'conflicts'];\n\n // Debug logging for order validation (only in development)\n if (process.env.NODE_ENV !== 'production') {\n const actualOrder: string[] = [];\n if (indicators.stashed > 0) actualOrder.push('stashed');\n if (indicators.renamed > 0) actualOrder.push('renamed');\n if (indicators.modified > 0) actualOrder.push('modified');\n if (indicators.staged > 0) actualOrder.push('staged');\n if (indicators.untracked > 0) actualOrder.push('untracked');\n if (indicators.deleted > 0) actualOrder.push('deleted');\n if (indicators.conflicts > 0) actualOrder.push('conflicts');\n\n // Validate order consistency\n for (let i = 0; i < actualOrder.length - 1; i++) {\n const currentIndex = expectedOrder.indexOf(actualOrder[i]!);\n const nextIndex = expectedOrder.indexOf(actualOrder[i + 1]!);\n if (currentIndex !== -1 && nextIndex !== -1 && currentIndex > nextIndex) {\n console.warn(`[WARN] Indicator order violation: ${actualOrder[i]!} should not come before ${actualOrder[i + 1]!}`);\n }\n }\n }\n\n if (indicators.stashed > 0) indicatorChars.push(symbols.stashed);\n if (indicators.renamed > 0) indicatorChars.push(symbols.renamed);\n if (indicators.modified > 0) indicatorChars.push('!');\n if (indicators.staged > 0) indicatorChars.push(symbols.staged);\n if (indicators.untracked > 0) indicatorChars.push('?');\n if (indicators.deleted > 0) indicatorChars.push(symbols.deleted);\n if (indicators.conflicts > 0) indicatorChars.push(symbols.conflict);\n\n // Ahead/behind status\n if (indicators.diverged) {\n indicatorChars.push(symbols.diverged);\n } else {\n if (indicators.ahead > 0) indicatorChars.push(symbols.ahead);\n if (indicators.behind > 0) indicatorChars.push(symbols.behind);\n }\n\n return indicatorChars.join('');\n }\n\n /**\n * Get git status string for display\n */\n formatGitStatus(gitInfo: GitInfo, symbols: Config['symbols']): string {\n const indicators = this.formatIndicators(gitInfo.indicators, symbols);\n const gitSymbol = this.config.noEmoji ? symbols.git : symbols.git;\n\n if (indicators) {\n return ` ${gitSymbol} ${gitInfo.branch} [${indicators}]`;\n } else {\n return ` ${gitSymbol} ${gitInfo.branch}`;\n }\n }\n}"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{readFileSync as bt}from"fs";import{z as l}from"zod";import{readFileSync as X,existsSync as Z}from"fs";import{homedir as q}from"os";import{join as P,dirname as Q}from"path";import{parse as tt}from"yaml";var I=l.object({cacheTTL:l.number().default(300),cacheDir:l.string().default("/tmp/.claude-statusline-cache"),maxLength:l.number().default(1e3),noEmoji:l.boolean().default(!1),noGitStatus:l.boolean().default(!1),noContextWindow:l.boolean().default(!1),envContext:l.boolean().default(!1),truncate:l.boolean().default(!1),softWrap:l.boolean().default(!1),noSoftWrap:l.boolean().default(!1),forceWidth:l.number().optional(),debugWidth:l.boolean().default(!1),rightMargin:l.number().default(15),symbols:l.object({git:l.string().default("\uF418"),model:l.string().default("\u{F06A9}"),contextWindow:l.string().default("\u26A1\uFE0E"),staged:l.string().default("+"),conflict:l.string().default("\xD7"),stashed:l.string().default("\u2691"),ahead:l.string().default("\u21E1"),behind:l.string().default("\u21E3"),diverged:l.string().default("\u21D5"),renamed:l.string().default("\xBB"),deleted:l.string().default("\u2718")}).default({}),asciiSymbols:l.object({git:l.string().default("@"),model:l.string().default("*"),contextWindow:l.string().default("#"),staged:l.string().default("+"),conflict:l.string().default("C"),stashed:l.string().default("$"),ahead:l.string().default("A"),behind:l.string().default("B"),diverged:l.string().default("D"),renamed:l.string().default(">"),deleted:l.string().default("X")}).default({})}),et=I.parse({}),rt=["claude-statusline.json","claude-statusline.yaml"];function O(r=process.cwd()){let t={...et};return t={...t,...nt(r)},t={...t,...ot()},I.parse(t)}function nt(r){let t=[r,Q(r),P(q(),".claude")];for(let e of t)for(let n of rt){let o=P(e,n);if(Z(o))try{let a=X(o,"utf-8");if(n.endsWith(".json"))return JSON.parse(a);if(n.endsWith(".yaml"))return tt(a)}catch{}}return{}}function ot(){let r={};if(process.env.CLAUDE_CODE_STATUSLINE_NO_EMOJI==="1"&&(r.noEmoji=!0),process.env.CLAUDE_CODE_STATUSLINE_NO_GITSTATUS==="1"&&(r.noGitStatus=!0),process.env.CLAUDE_CODE_STATUSLINE_NO_CONTEXT_WINDOW==="1"&&(r.noContextWindow=!0),process.env.CLAUDE_CODE_STATUSLINE_ENV_CONTEXT==="1"&&(r.envContext=!0),process.env.CLAUDE_CODE_STATUSLINE_TRUNCATE==="1"&&(r.truncate=!0),process.env.CLAUDE_CODE_STATUSLINE_SOFT_WRAP==="1"&&(r.softWrap=!0),process.env.CLAUDE_CODE_STATUSLINE_NO_SOFT_WRAP==="1"&&(r.noSoftWrap=!0),process.env.CLAUDE_CODE_STATUSLINE_FORCE_WIDTH){let t=parseInt(process.env.CLAUDE_CODE_STATUSLINE_FORCE_WIDTH,10);!isNaN(t)&&t>0&&(r.forceWidth=t)}return process.env.CLAUDE_CODE_STATUSLINE_DEBUG_WIDTH==="1"&&(r.debugWidth=!0),process.env.CLAUDE_CODE_STATUSLINE_CACHE_DIR&&(r.cacheDir=process.env.CLAUDE_CODE_STATUSLINE_CACHE_DIR),r}var st=4096,it=[/\.\./,/\.\.\\/,/\[/,/;/,/&/,/</,/>/,/`/];function R(r,t){if(!r||typeof r!="string")return!1;let e=r.trimEnd();if(e.length===0||e.length>t.maxLength||!e.includes("{")||!e.includes("}"))return!1;let n=(e.match(/"/g)||[]).length;if(n===0||n%2!==0)return!1;try{JSON.parse(e)}catch{return!1}return!0}function at(r){if(!r||typeof r!="string"||r.length>st)return!1;for(let t of it)if(t.test(r))return!1;if(r.includes("${")||r.includes("`")||r.includes("$("))return!1;try{let t=r.replace(/\/+/g,"/").replace(/\\+/g,"\\");if(t.includes("../")||t.includes("..\\")||t.startsWith("/")&&!t.startsWith("/home/")&&!t.startsWith("/Users/")&&!t.startsWith("/tmp/")&&!["/home","/Users","/tmp","/var","/opt"].some(o=>t.startsWith(o)))return!1;if(t.includes(":")&&/^[A-Za-z]:/.test(t)){let e=t.charAt(0).toUpperCase();if(e<"C"||e>"Z")return!1}}catch{return!1}return!0}async function W(r){if(!at(r))return!1;try{let{access:t}=await import("fs/promises"),{constants:e}=await import("fs");return await t(r,e.R_OK),!0}catch{return!1}}import{readFile as $,writeFile as x,mkdir as ct}from"fs/promises";import{existsSync as A}from"fs";import{join as C}from"path";var w=class{config;constructor(t){this.config=t}async ensureCacheDir(){try{await ct(this.config.cacheDir,{recursive:!0})}catch{}}getCachePath(t){return C(this.config.cacheDir,t)}getTimestampPath(t){return C(this.config.cacheDir,`${t}.time`)}async get(t,e=this.config.cacheTTL){let n=this.getCachePath(t),o=this.getTimestampPath(t);try{if(!A(n)||!A(o))return null;let a=await $(o,"utf-8"),s=parseInt(a.trim(),10);if(isNaN(s)||Math.floor(Date.now()/1e3)-s>=e)return null;let d=await $(n,"utf-8");try{return JSON.parse(d)}catch{return d}}catch{return null}}async set(t,e){let n=this.getCachePath(t),o=this.getTimestampPath(t);try{await this.ensureCacheDir();let a;typeof e=="string"?a=e:a=JSON.stringify(e);let s=Math.floor(Date.now()/1e3);return await Promise.all([x(n,a,"utf-8"),x(o,s.toString(),"utf-8")]),!0}catch{return!1}}async has(t,e=this.config.cacheTTL){return await this.get(t,e)!==null}async delete(t){let e=this.getCachePath(t),n=this.getTimestampPath(t);try{let{unlink:o}=await import("fs/promises");return await Promise.allSettled([o(e),o(n)]),!0}catch{return!1}}async clear(){try{let{readdir:t,unlink:e}=await import("fs/promises"),n=await t(this.config.cacheDir);return await Promise.allSettled(n.map(o=>e(C(this.config.cacheDir,o)))),!0}catch{return!1}}async getStats(){try{let{readdir:t,stat:e}=await import("fs/promises"),n=await t(this.config.cacheDir),o=0,a=0;for(let s of n)if(!s.endsWith(".time")){a++;let i=C(this.config.cacheDir,s);try{let c=await e(i);o+=c.size}catch{}}return{total:a,size:o}}catch{return{total:0,size:0}}}},b={NODE_VERSION:"node_version",PYTHON_VERSION:"python_version",PYTHON3_VERSION:"python3_version",DOCKER_VERSION:"docker_version",GIT_REMOTE_URL:r=>`git_remote_${Buffer.from(r).toString("base64")}`,GIT_BRANCH:r=>`git_branch_${Buffer.from(r).toString("base64")}`};async function E(r,t,e,n=[],o=300){let a=await r.get(t,o);if(a!==null)return a;try{let{exec:s}=await import("child_process"),{promisify:i}=await import("util"),c=i(s),{stdout:u}=await c(`${e} ${n.join(" ")}`,{timeout:5e3,encoding:"utf-8"}),d=u.trim();return d&&await r.set(t,d),d}catch{return null}}import{spawn as lt}from"child_process";async function g(r,t={}){return new Promise((e,n)=>{let o=lt("git",r,{cwd:t.cwd||process.cwd(),stdio:["ignore","pipe","pipe"],timeout:t.timeout||5e3}),a="",s="";o.stdout.on("data",i=>{a+=i.toString()}),o.stderr.on("data",i=>{s+=i.toString()}),o.on("close",i=>{i===0?e(a):n(new Error(`Git command failed with code ${i}: ${s||a}`))}),o.on("error",i=>{n(new Error(`Failed to execute git command: ${i.message}`))})})}async function F(r){try{return await g(["rev-parse","--git-dir"],r?{cwd:r}:{}),!0}catch{return!1}}async function k(r){let t=r?{cwd:r}:{};try{let n=(await g(["branch","--show-current"],t)).trim();if(n)return n}catch{}try{let n=(await g(["rev-parse","--abbrev-ref","HEAD"],t)).trim();if(n&&n!=="HEAD")return n}catch{}try{let n=(await g(["branch","--no-color"],t)).split(`
|
|
3
|
+
`);for(let o of n)if(o.startsWith("* "))return o.substring(2).trim()}catch{}return null}async function M(r){return g(["status","--porcelain"],r?{cwd:r}:{})}async function U(r){try{return g(["stash","list"],r?{cwd:r}:{})}catch{return""}}async function G(r){try{return(await g(["rev-parse","--abbrev-ref","@{u}"],r?{cwd:r}:{})).trim()}catch{return""}}async function L(r){try{let n=(await g(["rev-list","--count","--left-right","@{u}...HEAD"],r?{cwd:r}:{})).trim().split(" ");if(n.length===2){let o=parseInt(n[0]||"0",10);return{ahead:parseInt(n[1]||"0",10),behind:o}}}catch{}return{ahead:0,behind:0}}var ut={stashed:0,staged:0,modified:0,untracked:0,renamed:0,deleted:0,conflicts:0,ahead:0,behind:0,diverged:!1},T=class{config;cache;constructor(t,e){this.config=t,this.cache=e}async getGitInfo(t){if(this.config.noGitStatus)return null;try{if(!await F(t))return null;let n=await this.getCurrentBranch(t);if(!n)return null;let o=await this.getGitIndicators(t);return{branch:n,indicators:o}}catch{return null}}async getCurrentBranch(t){let e=`${b.GIT_BRANCH(t)}_current`,n=await this.cache.get(e,60);if(n)return n;try{let o=await k(t);return o?(await this.cache.set(e,o),o):null}catch{return null}}async getGitIndicators(t){let e={...ut};try{let o=(await M(t)).split(`
|
|
4
|
+
`).map(i=>i.trimEnd()).filter(i=>i.length>0);for(let i of o){if(i.length<2)continue;let c=i.charAt(0),u=i.charAt(1);if(c==="U"||u==="U"||c==="A"&&u==="A"||c==="D"&&u==="D")e.conflicts++;else if(c==="?"&&u==="?")e.untracked++;else{switch(c){case"M":e.staged++;break;case"A":e.staged++;break;case"D":e.deleted++;break;case"R":e.renamed++;break;case"C":e.staged++;break}switch(u){case"M":e.modified++;break;case"D":e.deleted++;break;case"R":e.renamed++;break}}}e.stashed=await this.getStashedCount(t);let{ahead:a,behind:s}=await this.getAheadBehind(t);e.ahead=a,e.behind=s,e.diverged=a>0&&s>0}catch{}return e}async getStashedCount(t){try{return(await U(t)).trim().split(`
|
|
5
|
+
`).filter(n=>n.trim().length>0).length}catch{return 0}}async getAheadBehind(t){try{return await G(t)?await L(t):{ahead:0,behind:0}}catch{}return{ahead:0,behind:0}}formatIndicators(t,e){let n=[],o=["stashed","renamed","modified","staged","untracked","deleted","conflicts"];return t.stashed>0&&n.push(e.stashed),t.renamed>0&&n.push(e.renamed),t.modified>0&&n.push("!"),t.staged>0&&n.push(e.staged),t.untracked>0&&n.push("?"),t.deleted>0&&n.push(e.deleted),t.conflicts>0&&n.push(e.conflict),t.diverged?n.push(e.diverged):(t.ahead>0&&n.push(e.ahead),t.behind>0&&n.push(e.behind)),n.join("")}formatGitStatus(t,e){let n=this.formatIndicators(t.indicators,e),o=(this.config.noEmoji,e.git);return n?` ${o} ${t.branch} [${n}]`:` ${o} ${t.branch}`}};var B=new Map,dt=1,H={git:"@",model:"*",contextWindow:"#",staged:"+",conflict:"C",stashed:"$",ahead:"A",behind:"B",diverged:"D",renamed:">",deleted:"X"},ft={git:"\uF418",model:"\u{F06A9}",contextWindow:"\u26A1\uFE0E",staged:"+",conflict:"\xD7",stashed:"\u2691",ahead:"\u21E1",behind:"\u21E3",diverged:"\u21D5",renamed:"\xBB",deleted:"\u2718"};async function j(r){let t=process.env.NERD_FONT+"|"+process.env.TERM_PROGRAM+"|"+process.env.TERM,e=`${dt}:${r.noEmoji?"ascii":"nerd"}:${t}`,n=B.get(e);if(n&&Date.now()-n.timestamp<6e4)return n.symbols;let o;return r.noEmoji?o={...H,...r.symbols,...r.asciiSymbols}:(await mt()).hasNerdFont?o={...ft,...r.symbols}:o={...H,...r.asciiSymbols},B.set(e,{symbols:o,timestamp:Date.now()}),o}async function mt(){let r={hasNerdFont:!1,terminal:"",font:"",method:""};if(process.env.NERD_FONT==="1")return r.hasNerdFont=!0,r.method="NERD_FONT env var",r;let t=process.env.TERM_PROGRAM,e=process.env.TERM;if(t&&(r.terminal=t,r.method="TERM_PROGRAM detection",["vscode","ghostty","wezterm","iterm"].includes(t))||e&&(r.terminal=e,r.method="TERM detection",["alacritty","kitty","wezterm","ghostty","xterm-256color"].includes(e)))return r.hasNerdFont=!0,r;let n=await ht();if(n.hasNerdFont)return r.hasNerdFont=!0,r.font=n.font,r.method="font list detection",r;let o=await gt();return o.hasNerdFont?(r.hasNerdFont=!0,r.font=o.font,r.method="installation detection",r):pt().hasNerdFont?(r.hasNerdFont=!0,r.method="environment detection",r):((await yt()).hasNerdFont&&(r.hasNerdFont=!0,r.method="platform-specific detection"),r)}async function ht(){try{let{exec:r}=await import("child_process"),{promisify:t}=await import("util"),e=t(r),n="",o=process.platform;if(o==="linux"?n="fc-list":o==="darwin"&&(n="system_profiler SPFontsDataType 2>/dev/null || system_profiler SPFontsDataType"),n){let{stdout:a}=await e(n,{timeout:3e3,encoding:"utf-8"}),s=[/nerd font/i,/symbols only/i,/jetbrains mono.*nerd/i,/fira code.*nerd/i,/hack.*nerd/i,/source code pro.*nerd/i,/ubuntu mono.*nerd/i,/anonymous pro.*nerd/i];for(let i of s)if(i.test(a)){let c=a.match(/([^:\n]*)(?=\s*(nerd|symbols))/i);return{hasNerdFont:!0,font:(c?c[1]:"Nerd Font")?.trim()||"Nerd Font"}}}}catch{}return{hasNerdFont:!1,font:""}}async function gt(){try{let{access:r,readdir:t}=await import("fs/promises"),{homedir:e}=await import("os"),n=process.platform,o=[];n==="darwin"?o.push(`${e()}/Library/Fonts`,"/System/Library/Fonts","/Library/Fonts"):n==="linux"&&o.push(`${e()}/.local/share/fonts`,`${e()}/.fonts`,"/usr/share/fonts","/usr/local/share/fonts");let a=["jetbrains-mono-nerd-font","fira-code-nerd-font","hack-nerd-font","source-code-pro-nerd-font","ubuntu-mono-nerd-font","anonymous-pro-nerd-font"];for(let s of o)try{await r(s);let i=await t(s);for(let c of i){let u=c.toLowerCase();for(let d of a)if(u.includes(d))return{hasNerdFont:!0,font:c}}}catch{}}catch{}return{hasNerdFont:!1,font:""}}function pt(){let r=["POWERLINE_COMMAND","NERDFONTS","FONT_FAMILY"];for(let t of r){let e=process.env[t];if(e&&e.toLowerCase().includes("nerd"))return{hasNerdFont:!0}}return process.env.VSCODE_PID||process.env.TERM_PROGRAM==="vscode"||process.env.TERM_PROGRAM==="ghostty"||process.env.TERM_PROGRAM==="wezterm"?{hasNerdFont:!0}:{hasNerdFont:!1}}async function yt(){if(process.platform==="darwin")try{let{exec:t}=await import("child_process"),{promisify:e}=await import("util"),n=e(t),{stdout:o}=await n("brew list | grep -i font",{timeout:2e3,encoding:"utf-8"});if(o.includes("nerd"))return{hasNerdFont:!0}}catch{}return{hasNerdFont:!1}}function V(r){return{node:"\uE718",python:"\uE235",docker:"\uF308",git:r.git,model:r.model}}async function _(r){if(r.forceWidth&&r.forceWidth>0)return r.forceWidth;let t=process.env.COLUMNS;if(t){let i=parseInt(t,10);if(!isNaN(i)&&i>0)return i}if(process.stdout.columns&&process.stdout.columns>0)return process.stdout.columns;let e=await z("tput",["cols"]);if(e)return e;let n=await K();if(n)return n;let o=process.env.CLAUDE_CODE_TERMINAL_WIDTH;if(o){let i=parseInt(o,10);if(!isNaN(i)&&i>0)return i}let a=process.env.TERM_PROGRAM,s=process.env.TERM;return a==="vscode"&&process.env.VSCODE_PID||["ghostty","wezterm","iterm"].includes(a||"")||s&&["alacritty","kitty","wezterm","ghostty","xterm-256color"].includes(s)||process.env.WT_SESSION||process.env.WT_PROFILE_ID?120:80}async function z(r,t){try{let{exec:e}=await import("child_process"),{promisify:n}=await import("util"),o=n(e),{stdout:a}=await o(`${r} ${t.join(" ")}`,{timeout:1e3,encoding:"utf-8"}),s=parseInt(a.trim(),10);if(!isNaN(s)&&s>0)return s}catch{}return null}async function K(){try{let{exec:r}=await import("child_process"),{promisify:t}=await import("util"),e=t(r),{stdout:n}=await e("stty size",{timeout:1e3,encoding:"utf-8"}),o=n.trim().split(" ");if(o.length===2){let a=parseInt(o[1]||"0",10);if(!isNaN(a)&&a>0)return a}}catch{}return null}async function Y(r){if(!r.debugWidth)return;process.stdout.columns;let t=process.env.COLUMNS,e=await z("tput",["cols"]),n=await K(),o=await _(r)}function D(r,t){return r.length<=t?r:t<4?"..":`${r.substring(0,t-2)}..`}function J(r,t,e,n){if(r.length+t.length<=e)return"";let o=e-t.length-2;if(o>=5)return`${r.substring(0,o)}..${t}`;let a="",s=t.match(/\[([^\]]+)\]/);s&&(a=s[1]||"");let i=e-a.length-8;if(i>=8){let c=t.substring(0,Math.min(t.length,i));return`${r.substring(0,4)}..${c}..${a?` [${a}]`:""}`}return`${r.substring(0,e)}..`}var v=class{config;cache;constructor(t,e){this.config=t,this.cache=e}async getEnvironmentInfo(){if(!this.config.envContext)return null;let t={},[e,n,o]=await Promise.allSettled([this.getNodeVersion(),this.getPythonVersion(),this.getDockerVersion()]);return e.status==="fulfilled"&&e.value&&(t.node=e.value),n.status==="fulfilled"&&n.value&&(t.python=n.value),o.status==="fulfilled"&&o.value&&(t.docker=o.value),Object.keys(t).length===0?null:t}async getNodeVersion(){let t=b.NODE_VERSION,e=this.config.cacheTTL*96,n=await this.cache.get(t,e);if(n)return n;try{let o=await E(this.cache,t,"node",["--version"],e);return o?o.replace(/^v/,"").trim():null}catch{return null}}async getPythonVersion(){let t=b.PYTHON3_VERSION,e=b.PYTHON_VERSION,n=this.config.cacheTTL*96;try{let o=await E(this.cache,t,"python3",["--version"],n);if(o){let a=o.match(/(\d+\.\d+\.\d+)/);if(a)return a[1]||null}}catch{}try{let o=await E(this.cache,e,"python",["--version"],n);if(o){let a=o.match(/(\d+\.\d+\.\d+)/);if(a)return a[1]||null}}catch{}return null}async getDockerVersion(){let t="docker_version";try{let e=await E(this.cache,t,"docker",["--version"],this.config.cacheTTL*96);if(e){let n=e.match(/Docker version (\d+\.\d+\.\d+)/);if(n)return n[1]||null}return null}catch{return null}}formatEnvironmentInfo(t,e){let n=[];return t.node&&n.push(`${e.node}${t.node}`),t.python&&n.push(`${e.python}${t.python}`),t.docker&&n.push(`${e.docker}${t.docker}`),n.join(" ")}async getAdditionalTools(){return{}}async isToolAvailable(t){try{let{exec:e}=await import("child_process"),{promisify:n}=await import("util");return await n(e)(`command -v ${t}`,{timeout:2e3}),!0}catch{return!1}}getShellEnvironment(){let t=process.env.SHELL||"unknown",e;return t.includes("bash")?e=process.env.BASH_VERSION:t.includes("zsh")?e=process.env.ZSH_VERSION:t.includes("fish")&&(e=process.env.FISH_VERSION),e?{shell:t,shellVersion:e}:{shell:t}}getOSInfo(){let t=process.platform,e=process.arch,n=process.env.OSTYPE||process.env.OS;return n?{platform:t,arch:e,release:n}:{platform:t,arch:e}}},N=class{symbols;constructor(t){this.symbols=t}format(t,e="compact"){switch(e){case"compact":return this.formatCompact(t);case"verbose":return this.formatVerbose(t);case"minimal":return this.formatMinimal(t);default:return this.formatCompact(t)}}formatCompact(t){let e=[];return t.node&&e.push(`Node${t.node}`),t.python&&e.push(`Py${t.python}`),t.docker&&e.push(`Docker${t.docker}`),e.join(" ")}formatVerbose(t){let e=[];return t.node&&e.push(`Node.js v${t.node}`),t.python&&e.push(`Python ${t.python}`),t.docker&&e.push(`Docker ${t.docker}`),e.join(" \u2022 ")}formatMinimal(t){let e=[];if(t.node){let n=t.node.split(".")[0];e.push(`N${n}`)}if(t.python){let n=t.python.split(".")[0];e.push(`P${n}`)}if(t.docker){let n=t.docker.split(".")[0];e.push(`D${n}`)}return e.join(" ")}formatWithIcons(t){let e=[];return t.node&&e.push(`${this.symbols.node}${t.node}`),t.python&&e.push(`${this.symbols.python}${t.python}`),t.docker&&e.push(`${this.symbols.docker}${t.docker}`),e.join(" ")}};async function Et(){try{let r=O(),t=new w(r),e=new T(r,t),n=new v(r,t);await Y(r);let o=await St();R(JSON.stringify(o),r)||process.exit(1);let{fullDir:a,modelName:s,contextWindow:i}=Ct(o);(!a||!s)&&process.exit(1),await W(a)||process.exit(1);let u=[e.getGitInfo(a),n.getEnvironmentInfo(),j(r)],d;r.truncate&&u.push(_(r));let f=await Promise.all(u),[h,p,m]=f;r.truncate&&f.length>3&&(d=f[3]);let y=await wt({fullDir:a,modelName:s,contextWindow:i,gitInfo:h,envInfo:p,symbols:m,...d&&{terminalWidth:d},config:r,gitOps:e});process.stdout.write(y)}catch{process.exit(1)}}async function St(){try{let r=bt(0,"utf-8");return JSON.parse(r.trim())}catch(r){throw new Error(`Failed to read or parse input: ${r instanceof Error?r.message:String(r)}`)}}function Ct(r){let t=r.workspace?.current_dir||"",e=r.model?.display_name||"Unknown",n=r.context_window;return{fullDir:t,modelName:e,contextWindow:n}}async function wt(r){let{fullDir:t,modelName:e,contextWindow:n,gitInfo:o,envInfo:a,symbols:s,terminalWidth:i,config:c,gitOps:u}=r,d=t.split("/").pop()||t.split("\\").pop()||"project",f="";o&&(f=u.formatGitStatus(o,s));let h="";if(a){let S=V(s);h=` ${new N(S).formatWithIcons(a)}`}let p="";if(n&&!c.noContextWindow){let S=Tt(n);S!==null&&(p=` ${s.contextWindow}${S}%`)}let m=`${s.model}${e}${h}${p}`,y=`${d}${f} ${m}`;return c.truncate&&(i||process.exit(1),y=vt({statusline:y,projectName:d,gitStatus:f,modelString:m,terminalWidth:i,config:c,symbols:s})),y}function Tt(r){try{let{current_usage:t,context_window_size:e}=r;if(!e||e===0)return null;if(!t)return 0;let n=t.input_tokens+(t.cache_creation_input_tokens||0)+(t.cache_read_input_tokens||0),o=Math.round(n/e*100);return Math.max(0,Math.min(100,o))}catch{return 0}}function vt(r){let{statusline:t,projectName:e,gitStatus:n,modelString:o,terminalWidth:a,config:s}=r,i=Math.max(a-s.rightMargin,30),c=`${e}${n}`;if(t.length<=i)return t;if(c.length+1<=i)if(s.noSoftWrap){let d=i-c.length-1,f=D(o,d);return`${c} ${f}`}else{let d=i-c.length-1;if(/^[*]/.test(o)&&o.length>d)return`${c}
|
|
6
|
+
${o}`;let h=Nt(o,d);return`${c} ${h}`}let u=J(e,n,i,s);return u||D(t,i)}function Nt(r,t){if(r.length<=t)return r;if(/^[*]/.test(r))return _t(r,t);let n=!1,o=Array.from(r),a=0,s=o.length,i=-1;for(let m=0;m<o.length;m++)if(o[m]===" "&&(i=m),a++,a>t){i>=0?s=i:s=m,n=i>=0;break}if(s>=o.length)return r;let c=o.length>0?o[0]:"",u=c&&c!==" "&&Buffer.byteLength(c,"utf8")>1;if(!n&&t-a>-3&&!u)return r;if(u&&s<=2&&t>=3)for(let m=2;m<Math.min(o.length,t);m++){s=m,n=!0;break}let d=o.slice(0,s),f=o.slice(s);f.length>0&&f[0]===" "&&f.shift();let h=d.join(""),p=f.join("");return p?`${h}
|
|
7
|
+
${p}`:h}function _t(r,t){if(r.length<=t)return r;let e=Array.from(r),n=[];for(let s=0;s<e.length;s++)e[s]===" "&&n.push(s);let o=-1;for(let s=0;s<e.length;s++){let i=e[s];if(i==="\u26A1"||i==="#"){o=s;break}}if(o>0){let s=e.findIndex((i,c)=>c<o&&i===" ");if(s>0){let i=e.slice(s+1).join("");if(i.length<=t)return`${e.slice(0,s).join("")}
|
|
8
|
+
${i}`}}if(n.length>0){let s=n[0];if(s!==void 0&&s>1){let i=e.slice(0,s).join("");if(i.length<=t){let c=e.slice(s+1).join("");return c?`${i}
|
|
9
|
+
${c}`:i}}}let a=Math.min(5,t);if(a>=3&&e.length>a){let s=Math.min(t,e.length-1),i=e.slice(0,s).join(""),c=e.slice(s).join("");return c?`${i}
|
|
10
|
+
${c}`:i}if(t>=3){let s=e.slice(0,t).join(""),i=e.slice(t).join("");return i?`${s}
|
|
11
|
+
${i}`:s}return r}import.meta.url===`file://${process.argv[1]}`&&Et();export{Et as main};
|