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,223 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Simple caching system with TTL support
|
|
6
|
+
* Ported from bash implementation with Node.js optimizations
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Cache wrapper that handles TTL and file operations
|
|
10
|
+
*/
|
|
11
|
+
export class Cache {
|
|
12
|
+
config;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Ensure cache directory exists
|
|
18
|
+
*/
|
|
19
|
+
async ensureCacheDir() {
|
|
20
|
+
try {
|
|
21
|
+
await mkdir(this.config.cacheDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
// Directory might already exist or we can't create it
|
|
25
|
+
console.warn('[WARNING] Failed to create cache directory:', this.config.cacheDir);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get cache file path for a given key
|
|
30
|
+
*/
|
|
31
|
+
getCachePath(key) {
|
|
32
|
+
return join(this.config.cacheDir, key);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get cache timestamp file path for a given key
|
|
36
|
+
*/
|
|
37
|
+
getTimestampPath(key) {
|
|
38
|
+
return join(this.config.cacheDir, `${key}.time`);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read cached data with TTL validation
|
|
42
|
+
*/
|
|
43
|
+
async get(key, ttl = this.config.cacheTTL) {
|
|
44
|
+
const cachePath = this.getCachePath(key);
|
|
45
|
+
const timestampPath = this.getTimestampPath(key);
|
|
46
|
+
try {
|
|
47
|
+
// Check if cache files exist
|
|
48
|
+
if (!existsSync(cachePath) || !existsSync(timestampPath)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// Read timestamp and check TTL
|
|
52
|
+
const timestampContent = await readFile(timestampPath, 'utf-8');
|
|
53
|
+
const timestamp = parseInt(timestampContent.trim(), 10);
|
|
54
|
+
if (isNaN(timestamp)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
58
|
+
const age = currentTime - timestamp;
|
|
59
|
+
// In development, reduce cache TTL to prevent stale data
|
|
60
|
+
const effectiveTTL = process.env.NODE_ENV === 'development' ? Math.min(ttl, 5) : ttl;
|
|
61
|
+
if (age >= effectiveTTL) {
|
|
62
|
+
// Cache expired
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
// Read cached data
|
|
66
|
+
const dataContent = await readFile(cachePath, 'utf-8');
|
|
67
|
+
// Try to parse as JSON, fallback to string
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(dataContent);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return dataContent;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// Any error reading cache should result in cache miss
|
|
77
|
+
console.debug('[DEBUG] Cache read error for key:', key, error instanceof Error ? error.message : String(error));
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Write data to cache with timestamp
|
|
83
|
+
*/
|
|
84
|
+
async set(key, data) {
|
|
85
|
+
const cachePath = this.getCachePath(key);
|
|
86
|
+
const timestampPath = this.getTimestampPath(key);
|
|
87
|
+
try {
|
|
88
|
+
// Ensure cache directory exists
|
|
89
|
+
await this.ensureCacheDir();
|
|
90
|
+
// Prepare data for storage
|
|
91
|
+
let dataContent;
|
|
92
|
+
if (typeof data === 'string') {
|
|
93
|
+
dataContent = data;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
dataContent = JSON.stringify(data);
|
|
97
|
+
}
|
|
98
|
+
// Write data and timestamp
|
|
99
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
100
|
+
await Promise.all([
|
|
101
|
+
writeFile(cachePath, dataContent, 'utf-8'),
|
|
102
|
+
writeFile(timestampPath, currentTime.toString(), 'utf-8'),
|
|
103
|
+
]);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.warn('[WARNING] Failed to write cache for key:', key, error instanceof Error ? error.message : String(error));
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if cache entry exists and is valid
|
|
113
|
+
*/
|
|
114
|
+
async has(key, ttl = this.config.cacheTTL) {
|
|
115
|
+
const value = await this.get(key, ttl);
|
|
116
|
+
return value !== null;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Delete cache entry
|
|
120
|
+
*/
|
|
121
|
+
async delete(key) {
|
|
122
|
+
const cachePath = this.getCachePath(key);
|
|
123
|
+
const timestampPath = this.getTimestampPath(key);
|
|
124
|
+
try {
|
|
125
|
+
const { unlink } = await import('fs/promises');
|
|
126
|
+
await Promise.allSettled([
|
|
127
|
+
unlink(cachePath),
|
|
128
|
+
unlink(timestampPath),
|
|
129
|
+
]);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.warn('[WARNING] Failed to delete cache for key:', key, error instanceof Error ? error.message : String(error));
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Clear all cache entries
|
|
139
|
+
*/
|
|
140
|
+
async clear() {
|
|
141
|
+
try {
|
|
142
|
+
const { readdir, unlink } = await import('fs/promises');
|
|
143
|
+
const files = await readdir(this.config.cacheDir);
|
|
144
|
+
await Promise.allSettled(files.map(file => unlink(join(this.config.cacheDir, file))));
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.warn('[WARNING] Failed to clear cache:', error instanceof Error ? error.message : String(error));
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get cache statistics
|
|
154
|
+
*/
|
|
155
|
+
async getStats() {
|
|
156
|
+
try {
|
|
157
|
+
const { readdir, stat } = await import('fs/promises');
|
|
158
|
+
const files = await readdir(this.config.cacheDir);
|
|
159
|
+
let totalSize = 0;
|
|
160
|
+
let cacheFiles = 0;
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
if (!file.endsWith('.time')) {
|
|
163
|
+
cacheFiles++;
|
|
164
|
+
const filePath = join(this.config.cacheDir, file);
|
|
165
|
+
try {
|
|
166
|
+
const stats = await stat(filePath);
|
|
167
|
+
totalSize += stats.size;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Skip files that can't be stat'ed
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { total: cacheFiles, size: totalSize };
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
console.warn('[WARNING] Failed to get cache stats:', error instanceof Error ? error.message : String(error));
|
|
178
|
+
return { total: 0, size: 0 };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Cache key generators for common use cases
|
|
184
|
+
*/
|
|
185
|
+
export const CacheKeys = {
|
|
186
|
+
NODE_VERSION: 'node_version',
|
|
187
|
+
PYTHON_VERSION: 'python_version',
|
|
188
|
+
PYTHON3_VERSION: 'python3_version',
|
|
189
|
+
DOCKER_VERSION: 'docker_version',
|
|
190
|
+
GIT_REMOTE_URL: (dir) => `git_remote_${Buffer.from(dir).toString('base64')}`,
|
|
191
|
+
GIT_BRANCH: (dir) => `git_branch_${Buffer.from(dir).toString('base64')}`,
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Cached command execution helper
|
|
195
|
+
* Runs a command and caches the result
|
|
196
|
+
*/
|
|
197
|
+
export async function cachedCommand(cache, key, command, args = [], ttl = 300) {
|
|
198
|
+
// Try to get from cache first
|
|
199
|
+
const cached = await cache.get(key, ttl);
|
|
200
|
+
if (cached !== null) {
|
|
201
|
+
return cached;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const { exec } = await import('child_process');
|
|
205
|
+
const { promisify } = await import('util');
|
|
206
|
+
const execAsync = promisify(exec);
|
|
207
|
+
const { stdout } = await execAsync(`${command} ${args.join(' ')}`, {
|
|
208
|
+
timeout: 5000, // 5 second timeout
|
|
209
|
+
encoding: 'utf-8',
|
|
210
|
+
});
|
|
211
|
+
const result = stdout.trim();
|
|
212
|
+
if (result) {
|
|
213
|
+
// Cache the successful result
|
|
214
|
+
await cache.set(key, result);
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
console.debug('[DEBUG] Command execution failed:', command, error instanceof Error ? error.message : String(error));
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/core/cache.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile, writeFile, mkdir } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { Config } from './config.js';\n\n/**\n * Simple caching system with TTL support\n * Ported from bash implementation with Node.js optimizations\n */\n\n\n/**\n * Cache wrapper that handles TTL and file operations\n */\nexport class Cache {\n private config: Config;\n\n constructor(config: Config) {\n this.config = config;\n }\n\n /**\n * Ensure cache directory exists\n */\n private async ensureCacheDir(): Promise<void> {\n try {\n await mkdir(this.config.cacheDir, { recursive: true });\n } catch (error) {\n // Directory might already exist or we can't create it\n console.warn('[WARNING] Failed to create cache directory:', this.config.cacheDir);\n }\n }\n\n /**\n * Get cache file path for a given key\n */\n private getCachePath(key: string): string {\n return join(this.config.cacheDir, key);\n }\n\n /**\n * Get cache timestamp file path for a given key\n */\n private getTimestampPath(key: string): string {\n return join(this.config.cacheDir, `${key}.time`);\n }\n\n /**\n * Read cached data with TTL validation\n */\n async get<T = string>(key: string, ttl: number = this.config.cacheTTL): Promise<T | null> {\n const cachePath = this.getCachePath(key);\n const timestampPath = this.getTimestampPath(key);\n\n try {\n // Check if cache files exist\n if (!existsSync(cachePath) || !existsSync(timestampPath)) {\n return null;\n }\n\n // Read timestamp and check TTL\n const timestampContent = await readFile(timestampPath, 'utf-8');\n const timestamp = parseInt(timestampContent.trim(), 10);\n\n if (isNaN(timestamp)) {\n return null;\n }\n\n const currentTime = Math.floor(Date.now() / 1000);\n const age = currentTime - timestamp;\n\n // In development, reduce cache TTL to prevent stale data\n const effectiveTTL = process.env.NODE_ENV === 'development' ? Math.min(ttl, 5) : ttl;\n\n if (age >= effectiveTTL) {\n // Cache expired\n return null;\n }\n\n // Read cached data\n const dataContent = await readFile(cachePath, 'utf-8');\n\n // Try to parse as JSON, fallback to string\n try {\n return JSON.parse(dataContent) as T;\n } catch {\n return dataContent as T;\n }\n\n } catch (error) {\n // Any error reading cache should result in cache miss\n console.debug('[DEBUG] Cache read error for key:', key, error instanceof Error ? error.message : String(error));\n return null;\n }\n }\n\n /**\n * Write data to cache with timestamp\n */\n async set<T = string>(key: string, data: T): Promise<boolean> {\n const cachePath = this.getCachePath(key);\n const timestampPath = this.getTimestampPath(key);\n\n try {\n // Ensure cache directory exists\n await this.ensureCacheDir();\n\n // Prepare data for storage\n let dataContent: string;\n if (typeof data === 'string') {\n dataContent = data;\n } else {\n dataContent = JSON.stringify(data);\n }\n\n // Write data and timestamp\n const currentTime = Math.floor(Date.now() / 1000);\n\n await Promise.all([\n writeFile(cachePath, dataContent, 'utf-8'),\n writeFile(timestampPath, currentTime.toString(), 'utf-8'),\n ]);\n\n return true;\n\n } catch (error) {\n console.warn('[WARNING] Failed to write cache for key:', key, error instanceof Error ? error.message : String(error));\n return false;\n }\n }\n\n /**\n * Check if cache entry exists and is valid\n */\n async has(key: string, ttl: number = this.config.cacheTTL): Promise<boolean> {\n const value = await this.get(key, ttl);\n return value !== null;\n }\n\n /**\n * Delete cache entry\n */\n async delete(key: string): Promise<boolean> {\n const cachePath = this.getCachePath(key);\n const timestampPath = this.getTimestampPath(key);\n\n try {\n const { unlink } = await import('fs/promises');\n\n await Promise.allSettled([\n unlink(cachePath),\n unlink(timestampPath),\n ]);\n\n return true;\n } catch (error) {\n console.warn('[WARNING] Failed to delete cache for key:', key, error instanceof Error ? error.message : String(error));\n return false;\n }\n }\n\n /**\n * Clear all cache entries\n */\n async clear(): Promise<boolean> {\n try {\n const { readdir, unlink } = await import('fs/promises');\n const files = await readdir(this.config.cacheDir);\n\n await Promise.allSettled(\n files.map(file => unlink(join(this.config.cacheDir, file)))\n );\n\n return true;\n } catch (error) {\n console.warn('[WARNING] Failed to clear cache:', error instanceof Error ? error.message : String(error));\n return false;\n }\n }\n\n /**\n * Get cache statistics\n */\n async getStats(): Promise<{ total: number; size: number }> {\n try {\n const { readdir, stat } = await import('fs/promises');\n const files = await readdir(this.config.cacheDir);\n\n let totalSize = 0;\n let cacheFiles = 0;\n\n for (const file of files) {\n if (!file.endsWith('.time')) {\n cacheFiles++;\n const filePath = join(this.config.cacheDir, file);\n try {\n const stats = await stat(filePath);\n totalSize += stats.size;\n } catch {\n // Skip files that can't be stat'ed\n }\n }\n }\n\n return { total: cacheFiles, size: totalSize };\n } catch (error) {\n console.warn('[WARNING] Failed to get cache stats:', error instanceof Error ? error.message : String(error));\n return { total: 0, size: 0 };\n }\n }\n}\n\n/**\n * Cache key generators for common use cases\n */\nexport const CacheKeys = {\n NODE_VERSION: 'node_version',\n PYTHON_VERSION: 'python_version',\n PYTHON3_VERSION: 'python3_version',\n DOCKER_VERSION: 'docker_version',\n GIT_REMOTE_URL: (dir: string) => `git_remote_${Buffer.from(dir).toString('base64')}`,\n GIT_BRANCH: (dir: string) => `git_branch_${Buffer.from(dir).toString('base64')}`,\n} as const;\n\n/**\n * Cached command execution helper\n * Runs a command and caches the result\n */\nexport async function cachedCommand(\n cache: Cache,\n key: string,\n command: string,\n args: string[] = [],\n ttl: number = 300\n): Promise<string | null> {\n // Try to get from cache first\n const cached = await cache.get<string>(key, ttl);\n if (cached !== null) {\n return cached;\n }\n\n try {\n const { exec } = await import('child_process');\n const { promisify } = await import('util');\n const execAsync = promisify(exec);\n\n const { stdout } = await execAsync(`${command} ${args.join(' ')}`, {\n timeout: 5000, // 5 second timeout\n encoding: 'utf-8',\n });\n\n const result = stdout.trim();\n if (result) {\n // Cache the successful result\n await cache.set(key, result);\n }\n\n return result;\n\n } catch (error) {\n console.debug('[DEBUG] Command execution failed:', command, error instanceof Error ? error.message : String(error));\n return null;\n }\n}"]}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration schema for claude-statusline
|
|
4
|
+
*/
|
|
5
|
+
export declare const ConfigSchema: z.ZodObject<{
|
|
6
|
+
cacheTTL: z.ZodDefault<z.ZodNumber>;
|
|
7
|
+
cacheDir: z.ZodDefault<z.ZodString>;
|
|
8
|
+
maxLength: z.ZodDefault<z.ZodNumber>;
|
|
9
|
+
noEmoji: z.ZodDefault<z.ZodBoolean>;
|
|
10
|
+
noGitStatus: z.ZodDefault<z.ZodBoolean>;
|
|
11
|
+
noContextWindow: z.ZodDefault<z.ZodBoolean>;
|
|
12
|
+
envContext: z.ZodDefault<z.ZodBoolean>;
|
|
13
|
+
truncate: z.ZodDefault<z.ZodBoolean>;
|
|
14
|
+
softWrap: z.ZodDefault<z.ZodBoolean>;
|
|
15
|
+
noSoftWrap: z.ZodDefault<z.ZodBoolean>;
|
|
16
|
+
forceWidth: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
debugWidth: z.ZodDefault<z.ZodBoolean>;
|
|
18
|
+
rightMargin: z.ZodDefault<z.ZodNumber>;
|
|
19
|
+
symbols: z.ZodDefault<z.ZodObject<{
|
|
20
|
+
git: z.ZodDefault<z.ZodString>;
|
|
21
|
+
model: z.ZodDefault<z.ZodString>;
|
|
22
|
+
contextWindow: z.ZodDefault<z.ZodString>;
|
|
23
|
+
staged: z.ZodDefault<z.ZodString>;
|
|
24
|
+
conflict: z.ZodDefault<z.ZodString>;
|
|
25
|
+
stashed: z.ZodDefault<z.ZodString>;
|
|
26
|
+
ahead: z.ZodDefault<z.ZodString>;
|
|
27
|
+
behind: z.ZodDefault<z.ZodString>;
|
|
28
|
+
diverged: z.ZodDefault<z.ZodString>;
|
|
29
|
+
renamed: z.ZodDefault<z.ZodString>;
|
|
30
|
+
deleted: z.ZodDefault<z.ZodString>;
|
|
31
|
+
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
git: string;
|
|
33
|
+
model: string;
|
|
34
|
+
contextWindow: string;
|
|
35
|
+
staged: string;
|
|
36
|
+
conflict: string;
|
|
37
|
+
stashed: string;
|
|
38
|
+
ahead: string;
|
|
39
|
+
behind: string;
|
|
40
|
+
diverged: string;
|
|
41
|
+
renamed: string;
|
|
42
|
+
deleted: string;
|
|
43
|
+
}, {
|
|
44
|
+
git?: string | undefined;
|
|
45
|
+
model?: string | undefined;
|
|
46
|
+
contextWindow?: string | undefined;
|
|
47
|
+
staged?: string | undefined;
|
|
48
|
+
conflict?: string | undefined;
|
|
49
|
+
stashed?: string | undefined;
|
|
50
|
+
ahead?: string | undefined;
|
|
51
|
+
behind?: string | undefined;
|
|
52
|
+
diverged?: string | undefined;
|
|
53
|
+
renamed?: string | undefined;
|
|
54
|
+
deleted?: string | undefined;
|
|
55
|
+
}>>;
|
|
56
|
+
asciiSymbols: z.ZodDefault<z.ZodObject<{
|
|
57
|
+
git: z.ZodDefault<z.ZodString>;
|
|
58
|
+
model: z.ZodDefault<z.ZodString>;
|
|
59
|
+
contextWindow: z.ZodDefault<z.ZodString>;
|
|
60
|
+
staged: z.ZodDefault<z.ZodString>;
|
|
61
|
+
conflict: z.ZodDefault<z.ZodString>;
|
|
62
|
+
stashed: z.ZodDefault<z.ZodString>;
|
|
63
|
+
ahead: z.ZodDefault<z.ZodString>;
|
|
64
|
+
behind: z.ZodDefault<z.ZodString>;
|
|
65
|
+
diverged: z.ZodDefault<z.ZodString>;
|
|
66
|
+
renamed: z.ZodDefault<z.ZodString>;
|
|
67
|
+
deleted: z.ZodDefault<z.ZodString>;
|
|
68
|
+
}, "strip", z.ZodTypeAny, {
|
|
69
|
+
git: string;
|
|
70
|
+
model: string;
|
|
71
|
+
contextWindow: string;
|
|
72
|
+
staged: string;
|
|
73
|
+
conflict: string;
|
|
74
|
+
stashed: string;
|
|
75
|
+
ahead: string;
|
|
76
|
+
behind: string;
|
|
77
|
+
diverged: string;
|
|
78
|
+
renamed: string;
|
|
79
|
+
deleted: string;
|
|
80
|
+
}, {
|
|
81
|
+
git?: string | undefined;
|
|
82
|
+
model?: string | undefined;
|
|
83
|
+
contextWindow?: string | undefined;
|
|
84
|
+
staged?: string | undefined;
|
|
85
|
+
conflict?: string | undefined;
|
|
86
|
+
stashed?: string | undefined;
|
|
87
|
+
ahead?: string | undefined;
|
|
88
|
+
behind?: string | undefined;
|
|
89
|
+
diverged?: string | undefined;
|
|
90
|
+
renamed?: string | undefined;
|
|
91
|
+
deleted?: string | undefined;
|
|
92
|
+
}>>;
|
|
93
|
+
}, "strip", z.ZodTypeAny, {
|
|
94
|
+
cacheTTL: number;
|
|
95
|
+
cacheDir: string;
|
|
96
|
+
maxLength: number;
|
|
97
|
+
noEmoji: boolean;
|
|
98
|
+
noGitStatus: boolean;
|
|
99
|
+
noContextWindow: boolean;
|
|
100
|
+
envContext: boolean;
|
|
101
|
+
truncate: boolean;
|
|
102
|
+
softWrap: boolean;
|
|
103
|
+
noSoftWrap: boolean;
|
|
104
|
+
debugWidth: boolean;
|
|
105
|
+
rightMargin: number;
|
|
106
|
+
symbols: {
|
|
107
|
+
git: string;
|
|
108
|
+
model: string;
|
|
109
|
+
contextWindow: string;
|
|
110
|
+
staged: string;
|
|
111
|
+
conflict: string;
|
|
112
|
+
stashed: string;
|
|
113
|
+
ahead: string;
|
|
114
|
+
behind: string;
|
|
115
|
+
diverged: string;
|
|
116
|
+
renamed: string;
|
|
117
|
+
deleted: string;
|
|
118
|
+
};
|
|
119
|
+
asciiSymbols: {
|
|
120
|
+
git: string;
|
|
121
|
+
model: string;
|
|
122
|
+
contextWindow: string;
|
|
123
|
+
staged: string;
|
|
124
|
+
conflict: string;
|
|
125
|
+
stashed: string;
|
|
126
|
+
ahead: string;
|
|
127
|
+
behind: string;
|
|
128
|
+
diverged: string;
|
|
129
|
+
renamed: string;
|
|
130
|
+
deleted: string;
|
|
131
|
+
};
|
|
132
|
+
forceWidth?: number | undefined;
|
|
133
|
+
}, {
|
|
134
|
+
cacheTTL?: number | undefined;
|
|
135
|
+
cacheDir?: string | undefined;
|
|
136
|
+
maxLength?: number | undefined;
|
|
137
|
+
noEmoji?: boolean | undefined;
|
|
138
|
+
noGitStatus?: boolean | undefined;
|
|
139
|
+
noContextWindow?: boolean | undefined;
|
|
140
|
+
envContext?: boolean | undefined;
|
|
141
|
+
truncate?: boolean | undefined;
|
|
142
|
+
softWrap?: boolean | undefined;
|
|
143
|
+
noSoftWrap?: boolean | undefined;
|
|
144
|
+
forceWidth?: number | undefined;
|
|
145
|
+
debugWidth?: boolean | undefined;
|
|
146
|
+
rightMargin?: number | undefined;
|
|
147
|
+
symbols?: {
|
|
148
|
+
git?: string | undefined;
|
|
149
|
+
model?: string | undefined;
|
|
150
|
+
contextWindow?: string | undefined;
|
|
151
|
+
staged?: string | undefined;
|
|
152
|
+
conflict?: string | undefined;
|
|
153
|
+
stashed?: string | undefined;
|
|
154
|
+
ahead?: string | undefined;
|
|
155
|
+
behind?: string | undefined;
|
|
156
|
+
diverged?: string | undefined;
|
|
157
|
+
renamed?: string | undefined;
|
|
158
|
+
deleted?: string | undefined;
|
|
159
|
+
} | undefined;
|
|
160
|
+
asciiSymbols?: {
|
|
161
|
+
git?: string | undefined;
|
|
162
|
+
model?: string | undefined;
|
|
163
|
+
contextWindow?: string | undefined;
|
|
164
|
+
staged?: string | undefined;
|
|
165
|
+
conflict?: string | undefined;
|
|
166
|
+
stashed?: string | undefined;
|
|
167
|
+
ahead?: string | undefined;
|
|
168
|
+
behind?: string | undefined;
|
|
169
|
+
diverged?: string | undefined;
|
|
170
|
+
renamed?: string | undefined;
|
|
171
|
+
deleted?: string | undefined;
|
|
172
|
+
} | undefined;
|
|
173
|
+
}>;
|
|
174
|
+
export type Config = z.infer<typeof ConfigSchema>;
|
|
175
|
+
/**
|
|
176
|
+
* Default configuration instance
|
|
177
|
+
*/
|
|
178
|
+
export declare const defaultConfig: Config;
|
|
179
|
+
/**
|
|
180
|
+
* Load configuration from file and environment variables
|
|
181
|
+
*/
|
|
182
|
+
export declare function loadConfig(cwd?: string): Config;
|
|
183
|
+
/**
|
|
184
|
+
* Get configuration file path for writing
|
|
185
|
+
*/
|
|
186
|
+
export declare function getConfigFilePath(cwd?: string): string | null;
|
|
187
|
+
/**
|
|
188
|
+
* Generate a sample configuration file
|
|
189
|
+
*/
|
|
190
|
+
export declare function generateSampleConfig(): string;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join, dirname } from 'path';
|
|
5
|
+
import { parse as parseYaml } from 'yaml';
|
|
6
|
+
/**
|
|
7
|
+
* Configuration schema for claude-statusline
|
|
8
|
+
*/
|
|
9
|
+
export const ConfigSchema = z.object({
|
|
10
|
+
// Core settings
|
|
11
|
+
cacheTTL: z.number().default(300), // 5 minutes
|
|
12
|
+
cacheDir: z.string().default('/tmp/.claude-statusline-cache'),
|
|
13
|
+
maxLength: z.number().default(1000), // Maximum input length
|
|
14
|
+
// Feature toggles
|
|
15
|
+
noEmoji: z.boolean().default(false), // Force ASCII mode
|
|
16
|
+
noGitStatus: z.boolean().default(false), // Disable git indicators
|
|
17
|
+
noContextWindow: z.boolean().default(false), // Disable context window usage
|
|
18
|
+
envContext: z.boolean().default(false), // Show environment versions
|
|
19
|
+
truncate: z.boolean().default(false), // Smart truncation
|
|
20
|
+
softWrap: z.boolean().default(false), // Soft wrapping (legacy)
|
|
21
|
+
noSoftWrap: z.boolean().default(false), // Disable soft-wrapping
|
|
22
|
+
// Width and display settings
|
|
23
|
+
forceWidth: z.number().optional(), // Manual width override
|
|
24
|
+
debugWidth: z.boolean().default(false), // Width debugging
|
|
25
|
+
rightMargin: z.number().default(15), // Right margin for Claude telemetry
|
|
26
|
+
// Symbol settings
|
|
27
|
+
symbols: z.object({
|
|
28
|
+
git: z.string().default(''),
|
|
29
|
+
model: z.string().default(''),
|
|
30
|
+
contextWindow: z.string().default('⚡︎'),
|
|
31
|
+
staged: z.string().default('+'),
|
|
32
|
+
conflict: z.string().default('×'),
|
|
33
|
+
stashed: z.string().default('⚑'),
|
|
34
|
+
ahead: z.string().default('⇡'),
|
|
35
|
+
behind: z.string().default('⇣'),
|
|
36
|
+
diverged: z.string().default('⇕'),
|
|
37
|
+
renamed: z.string().default('»'),
|
|
38
|
+
deleted: z.string().default('✘'),
|
|
39
|
+
}).default({}),
|
|
40
|
+
// ASCII fallback symbols
|
|
41
|
+
asciiSymbols: z.object({
|
|
42
|
+
git: z.string().default('@'),
|
|
43
|
+
model: z.string().default('*'),
|
|
44
|
+
contextWindow: z.string().default('#'),
|
|
45
|
+
staged: z.string().default('+'),
|
|
46
|
+
conflict: z.string().default('C'),
|
|
47
|
+
stashed: z.string().default('$'),
|
|
48
|
+
ahead: z.string().default('A'),
|
|
49
|
+
behind: z.string().default('B'),
|
|
50
|
+
diverged: z.string().default('D'),
|
|
51
|
+
renamed: z.string().default('>'),
|
|
52
|
+
deleted: z.string().default('X'),
|
|
53
|
+
}).default({}),
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* Default configuration instance
|
|
57
|
+
*/
|
|
58
|
+
export const defaultConfig = ConfigSchema.parse({});
|
|
59
|
+
/**
|
|
60
|
+
* Configuration file names to search for (in order of preference)
|
|
61
|
+
*/
|
|
62
|
+
const CONFIG_FILES = [
|
|
63
|
+
'claude-statusline.json',
|
|
64
|
+
'claude-statusline.yaml',
|
|
65
|
+
];
|
|
66
|
+
/**
|
|
67
|
+
* Load configuration from file and environment variables
|
|
68
|
+
*/
|
|
69
|
+
export function loadConfig(cwd = process.cwd()) {
|
|
70
|
+
let config = { ...defaultConfig };
|
|
71
|
+
// 1. Load from configuration file
|
|
72
|
+
config = { ...config, ...loadConfigFile(cwd) };
|
|
73
|
+
// 2. Override with environment variables
|
|
74
|
+
config = { ...config, ...loadEnvConfig() };
|
|
75
|
+
// Validate final configuration
|
|
76
|
+
return ConfigSchema.parse(config);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Load configuration from file in the current directory or home directory
|
|
80
|
+
*/
|
|
81
|
+
function loadConfigFile(cwd) {
|
|
82
|
+
// Search in current directory first, then parent directories, then ~/.claude/
|
|
83
|
+
const searchPaths = [cwd, dirname(cwd), join(homedir(), '.claude')];
|
|
84
|
+
for (const searchPath of searchPaths) {
|
|
85
|
+
for (const filename of CONFIG_FILES) {
|
|
86
|
+
const configPath = join(searchPath, filename);
|
|
87
|
+
if (existsSync(configPath)) {
|
|
88
|
+
try {
|
|
89
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
90
|
+
if (filename.endsWith('.json')) {
|
|
91
|
+
return JSON.parse(content);
|
|
92
|
+
}
|
|
93
|
+
else if (filename.endsWith('.yaml')) {
|
|
94
|
+
return parseYaml(content);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.warn(`[WARNING] Failed to parse config file ${configPath}:`, error instanceof Error ? error.message : String(error));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Load configuration from environment variables
|
|
107
|
+
* Maps v1.0 environment variables to new configuration format
|
|
108
|
+
*/
|
|
109
|
+
function loadEnvConfig() {
|
|
110
|
+
const env = {};
|
|
111
|
+
// Feature toggles
|
|
112
|
+
if (process.env.CLAUDE_CODE_STATUSLINE_NO_EMOJI === '1') {
|
|
113
|
+
env.noEmoji = true;
|
|
114
|
+
}
|
|
115
|
+
if (process.env.CLAUDE_CODE_STATUSLINE_NO_GITSTATUS === '1') {
|
|
116
|
+
env.noGitStatus = true;
|
|
117
|
+
}
|
|
118
|
+
if (process.env.CLAUDE_CODE_STATUSLINE_NO_CONTEXT_WINDOW === '1') {
|
|
119
|
+
env.noContextWindow = true;
|
|
120
|
+
}
|
|
121
|
+
if (process.env.CLAUDE_CODE_STATUSLINE_ENV_CONTEXT === '1') {
|
|
122
|
+
env.envContext = true;
|
|
123
|
+
}
|
|
124
|
+
if (process.env.CLAUDE_CODE_STATUSLINE_TRUNCATE === '1') {
|
|
125
|
+
env.truncate = true;
|
|
126
|
+
}
|
|
127
|
+
if (process.env.CLAUDE_CODE_STATUSLINE_SOFT_WRAP === '1') {
|
|
128
|
+
env.softWrap = true;
|
|
129
|
+
}
|
|
130
|
+
if (process.env.CLAUDE_CODE_STATUSLINE_NO_SOFT_WRAP === '1') {
|
|
131
|
+
env.noSoftWrap = true;
|
|
132
|
+
}
|
|
133
|
+
// Width settings
|
|
134
|
+
if (process.env.CLAUDE_CODE_STATUSLINE_FORCE_WIDTH) {
|
|
135
|
+
const width = parseInt(process.env.CLAUDE_CODE_STATUSLINE_FORCE_WIDTH, 10);
|
|
136
|
+
if (!isNaN(width) && width > 0) {
|
|
137
|
+
env.forceWidth = width;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (process.env.CLAUDE_CODE_STATUSLINE_DEBUG_WIDTH === '1') {
|
|
141
|
+
env.debugWidth = true;
|
|
142
|
+
}
|
|
143
|
+
// Cache directory override
|
|
144
|
+
if (process.env.CLAUDE_CODE_STATUSLINE_CACHE_DIR) {
|
|
145
|
+
env.cacheDir = process.env.CLAUDE_CODE_STATUSLINE_CACHE_DIR;
|
|
146
|
+
}
|
|
147
|
+
return env;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get configuration file path for writing
|
|
151
|
+
*/
|
|
152
|
+
export function getConfigFilePath(cwd = process.cwd()) {
|
|
153
|
+
// Prefer claude-statusline.json in the current directory
|
|
154
|
+
const configPath = join(cwd, 'claude-statusline.json');
|
|
155
|
+
return configPath;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Generate a sample configuration file
|
|
159
|
+
*/
|
|
160
|
+
export function generateSampleConfig() {
|
|
161
|
+
return JSON.stringify({
|
|
162
|
+
$schema: 'https://raw.githubusercontent.com/shrwnsan/claude-statusline/main/config-schema.json',
|
|
163
|
+
// Core settings
|
|
164
|
+
cacheTTL: 300, // 5 minutes
|
|
165
|
+
maxLength: 1000,
|
|
166
|
+
// Feature toggles
|
|
167
|
+
noEmoji: false, // Set to true to force ASCII mode
|
|
168
|
+
noGitStatus: false, // Set to true to disable git indicators
|
|
169
|
+
noContextWindow: false, // Set to true to disable context window usage
|
|
170
|
+
envContext: true, // Set to true to show Node.js, Python versions
|
|
171
|
+
truncate: true, // Set to true to enable smart truncation
|
|
172
|
+
softWrap: false, // Set to true to enable soft wrapping
|
|
173
|
+
// Display settings
|
|
174
|
+
rightMargin: 15, // Right margin for Claude telemetry compatibility
|
|
175
|
+
debugWidth: false, // Set to true for width debugging output
|
|
176
|
+
// Custom symbols (optional - will use defaults if not specified)
|
|
177
|
+
symbols: {
|
|
178
|
+
git: '',
|
|
179
|
+
model: '',
|
|
180
|
+
contextWindow: '⚡︎',
|
|
181
|
+
staged: '+',
|
|
182
|
+
conflict: '×',
|
|
183
|
+
stashed: '⚑',
|
|
184
|
+
ahead: '⇡',
|
|
185
|
+
behind: '⇣',
|
|
186
|
+
diverged: '⇕',
|
|
187
|
+
renamed: '»',
|
|
188
|
+
deleted: '✘',
|
|
189
|
+
},
|
|
190
|
+
}, null, 2);
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport { readFileSync, existsSync } from 'fs';\nimport { homedir } from 'os';\nimport { join, dirname } from 'path';\nimport { parse as parseYaml } from 'yaml';\n\n/**\n * Configuration schema for claude-statusline\n */\nexport const ConfigSchema = z.object({\n // Core settings\n cacheTTL: z.number().default(300), // 5 minutes\n cacheDir: z.string().default('/tmp/.claude-statusline-cache'),\n maxLength: z.number().default(1000), // Maximum input length\n\n // Feature toggles\n noEmoji: z.boolean().default(false), // Force ASCII mode\n noGitStatus: z.boolean().default(false), // Disable git indicators\n noContextWindow: z.boolean().default(false), // Disable context window usage\n envContext: z.boolean().default(false), // Show environment versions\n truncate: z.boolean().default(false), // Smart truncation\n softWrap: z.boolean().default(false), // Soft wrapping (legacy)\n noSoftWrap: z.boolean().default(false), // Disable soft-wrapping\n\n // Width and display settings\n forceWidth: z.number().optional(), // Manual width override\n debugWidth: z.boolean().default(false), // Width debugging\n rightMargin: z.number().default(15), // Right margin for Claude telemetry\n\n // Symbol settings\n symbols: z.object({\n git: z.string().default(''),\n model: z.string().default(''),\n contextWindow: z.string().default('⚡︎'),\n staged: z.string().default('+'),\n conflict: z.string().default('×'),\n stashed: z.string().default('⚑'),\n ahead: z.string().default('⇡'),\n behind: z.string().default('⇣'),\n diverged: z.string().default('⇕'),\n renamed: z.string().default('»'),\n deleted: z.string().default('✘'),\n }).default({}),\n\n // ASCII fallback symbols\n asciiSymbols: z.object({\n git: z.string().default('@'),\n model: z.string().default('*'),\n contextWindow: z.string().default('#'),\n staged: z.string().default('+'),\n conflict: z.string().default('C'),\n stashed: z.string().default('$'),\n ahead: z.string().default('A'),\n behind: z.string().default('B'),\n diverged: z.string().default('D'),\n renamed: z.string().default('>'),\n deleted: z.string().default('X'),\n }).default({}),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\n/**\n * Default configuration instance\n */\nexport const defaultConfig: Config = ConfigSchema.parse({});\n\n/**\n * Configuration file names to search for (in order of preference)\n */\nconst CONFIG_FILES = [\n 'claude-statusline.json',\n 'claude-statusline.yaml',\n];\n\n/**\n * Load configuration from file and environment variables\n */\nexport function loadConfig(cwd: string = process.cwd()): Config {\n let config = { ...defaultConfig };\n\n // 1. Load from configuration file\n config = { ...config, ...loadConfigFile(cwd) };\n\n // 2. Override with environment variables\n config = { ...config, ...loadEnvConfig() };\n\n // Validate final configuration\n return ConfigSchema.parse(config);\n}\n\n/**\n * Load configuration from file in the current directory or home directory\n */\nfunction loadConfigFile(cwd: string): Partial<Config> {\n // Search in current directory first, then parent directories, then ~/.claude/\n const searchPaths = [cwd, dirname(cwd), join(homedir(), '.claude')];\n\n for (const searchPath of searchPaths) {\n for (const filename of CONFIG_FILES) {\n const configPath = join(searchPath, filename);\n\n if (existsSync(configPath)) {\n try {\n const content = readFileSync(configPath, 'utf-8');\n\n if (filename.endsWith('.json')) {\n return JSON.parse(content);\n } else if (filename.endsWith('.yaml')) {\n return parseYaml(content);\n }\n } catch (error) {\n console.warn(`[WARNING] Failed to parse config file ${configPath}:`, error instanceof Error ? error.message : String(error));\n }\n }\n }\n }\n\n return {};\n}\n\n/**\n * Load configuration from environment variables\n * Maps v1.0 environment variables to new configuration format\n */\nfunction loadEnvConfig(): Partial<Config> {\n const env: Partial<Config> = {};\n\n // Feature toggles\n if (process.env.CLAUDE_CODE_STATUSLINE_NO_EMOJI === '1') {\n env.noEmoji = true;\n }\n\n if (process.env.CLAUDE_CODE_STATUSLINE_NO_GITSTATUS === '1') {\n env.noGitStatus = true;\n }\n\n if (process.env.CLAUDE_CODE_STATUSLINE_NO_CONTEXT_WINDOW === '1') {\n env.noContextWindow = true;\n }\n\n if (process.env.CLAUDE_CODE_STATUSLINE_ENV_CONTEXT === '1') {\n env.envContext = true;\n }\n\n if (process.env.CLAUDE_CODE_STATUSLINE_TRUNCATE === '1') {\n env.truncate = true;\n }\n\n if (process.env.CLAUDE_CODE_STATUSLINE_SOFT_WRAP === '1') {\n env.softWrap = true;\n }\n\n if (process.env.CLAUDE_CODE_STATUSLINE_NO_SOFT_WRAP === '1') {\n env.noSoftWrap = true;\n }\n\n // Width settings\n if (process.env.CLAUDE_CODE_STATUSLINE_FORCE_WIDTH) {\n const width = parseInt(process.env.CLAUDE_CODE_STATUSLINE_FORCE_WIDTH, 10);\n if (!isNaN(width) && width > 0) {\n env.forceWidth = width;\n }\n }\n\n if (process.env.CLAUDE_CODE_STATUSLINE_DEBUG_WIDTH === '1') {\n env.debugWidth = true;\n }\n\n // Cache directory override\n if (process.env.CLAUDE_CODE_STATUSLINE_CACHE_DIR) {\n env.cacheDir = process.env.CLAUDE_CODE_STATUSLINE_CACHE_DIR;\n }\n\n return env;\n}\n\n/**\n * Get configuration file path for writing\n */\nexport function getConfigFilePath(cwd: string = process.cwd()): string | null {\n // Prefer claude-statusline.json in the current directory\n const configPath = join(cwd, 'claude-statusline.json');\n return configPath;\n}\n\n/**\n * Generate a sample configuration file\n */\nexport function generateSampleConfig(): string {\n return JSON.stringify({\n $schema: 'https://raw.githubusercontent.com/shrwnsan/claude-statusline/main/config-schema.json',\n // Core settings\n cacheTTL: 300, // 5 minutes\n maxLength: 1000,\n\n // Feature toggles\n noEmoji: false, // Set to true to force ASCII mode\n noGitStatus: false, // Set to true to disable git indicators\n noContextWindow: false, // Set to true to disable context window usage\n envContext: true, // Set to true to show Node.js, Python versions\n truncate: true, // Set to true to enable smart truncation\n softWrap: false, // Set to true to enable soft wrapping\n\n // Display settings\n rightMargin: 15, // Right margin for Claude telemetry compatibility\n debugWidth: false, // Set to true for width debugging output\n\n // Custom symbols (optional - will use defaults if not specified)\n symbols: {\n git: '',\n model: '',\n contextWindow: '⚡︎',\n staged: '+',\n conflict: '×',\n stashed: '⚑',\n ahead: '⇡',\n behind: '⇣',\n diverged: '⇕',\n renamed: '»',\n deleted: '✘',\n },\n }, null, 2);\n}"]}
|