@owloops/claude-powerline 1.0.1 → 1.1.1

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/dist/index.js CHANGED
@@ -1,263 +1,899 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import process from "process";
5
- import path from "path";
6
- import fs from "fs";
7
- import { execSync as execSync2 } from "child_process";
8
- import os from "os";
4
+ import process2 from "process";
5
+ import path2 from "path";
6
+ import fs2 from "fs";
7
+ import { execSync as execSync3 } from "child_process";
8
+ import os2 from "os";
9
9
  import getStdin from "get-stdin";
10
10
 
11
- // src/powerline.ts
12
- import { execSync } from "child_process";
11
+ // src/lib/colors.ts
12
+ function hexToAnsi(hex, isBackground) {
13
+ const r = parseInt(hex.slice(1, 3), 16);
14
+ const g = parseInt(hex.slice(3, 5), 16);
15
+ const b = parseInt(hex.slice(5, 7), 16);
16
+ return `\x1B[${isBackground ? "48" : "38"};2;${r};${g};${b}m`;
17
+ }
18
+ function extractBgToFg(ansiCode) {
19
+ const match = ansiCode.match(/48;2;(\d+);(\d+);(\d+)/);
20
+ if (match) {
21
+ return `\x1B[38;2;${match[1]};${match[2]};${match[3]}m`;
22
+ }
23
+ return ansiCode.replace("48", "38");
24
+ }
25
+
26
+ // src/lib/usage-provider.ts
13
27
  import {
14
28
  loadSessionUsageById,
15
29
  loadDailyUsageData,
30
+ loadSessionBlockData,
16
31
  getClaudePaths
17
32
  } from "ccusage/data-loader";
18
- import { calculateTotals } from "ccusage/calculate-cost";
33
+ import { calculateTotals, getTotalTokens } from "ccusage/calculate-cost";
19
34
  import { logger } from "ccusage/logger";
20
- var PowerlineRenderer = class {
21
- symbols;
22
- colors;
23
- constructor() {
24
- this.symbols = this.initializeSymbols();
25
- this.colors = {
26
- colors: {
27
- reset: "\x1B[0m",
28
- modeBg: "\x1B[48;2;255;107;71m",
29
- modeFg: "\x1B[97m",
30
- sessionBg: "\x1B[48;2;79;179;217m",
31
- sessionFg: "\x1B[97m",
32
- dailyBg: "\x1B[48;2;135;206;235m",
33
- dailyFg: "\x1B[30m",
34
- blockBg: "\x1B[48;2;218;112;214m",
35
- blockFg: "\x1B[97m",
36
- burnLowBg: "\x1B[48;2;144;238;144m",
37
- burnFg: "\x1B[97m"
38
- },
39
- dark: {
40
- reset: "\x1B[0m",
41
- modeBg: "\x1B[48;2;139;69;19m",
42
- modeFg: "\x1B[97m",
43
- sessionBg: "\x1B[48;2;64;64;64m",
44
- sessionFg: "\x1B[97m",
45
- dailyBg: "\x1B[48;2;45;45;45m",
46
- dailyFg: "\x1B[97m",
47
- blockBg: "\x1B[48;2;32;32;32m",
48
- blockFg: "\x1B[96m",
49
- burnLowBg: "\x1B[48;2;28;28;28m",
50
- burnFg: "\x1B[97m"
35
+ var UsageProvider = class {
36
+ async getSessionBlockInfo() {
37
+ const originalLevel = logger.level;
38
+ logger.level = 0;
39
+ try {
40
+ const blocks = await loadSessionBlockData({
41
+ mode: "auto",
42
+ sessionDurationHours: 5
43
+ });
44
+ const activeBlock = blocks.find((block) => block.isActive);
45
+ if (!activeBlock) {
46
+ return null;
51
47
  }
52
- };
48
+ const now = /* @__PURE__ */ new Date();
49
+ const timeRemaining = Math.round(
50
+ (activeBlock.endTime.getTime() - now.getTime()) / (1e3 * 60)
51
+ );
52
+ const elapsed = Math.round(
53
+ (now.getTime() - activeBlock.startTime.getTime()) / (1e3 * 60)
54
+ );
55
+ const totalTokens = (activeBlock.tokenCounts?.inputTokens || 0) + (activeBlock.tokenCounts?.outputTokens || 0) + (activeBlock.tokenCounts?.cacheCreationInputTokens || 0) + (activeBlock.tokenCounts?.cacheReadInputTokens || 0);
56
+ const burnRate = elapsed > 0 ? activeBlock.costUSD / elapsed * 60 : null;
57
+ const tokenBurnRate = elapsed > 0 ? totalTokens / elapsed * 60 : null;
58
+ return {
59
+ cost: activeBlock.costUSD,
60
+ tokens: totalTokens,
61
+ timeRemaining: Math.max(0, timeRemaining),
62
+ burnRate,
63
+ tokenBurnRate,
64
+ isActive: true
65
+ };
66
+ } catch {
67
+ return null;
68
+ } finally {
69
+ logger.level = originalLevel;
70
+ }
53
71
  }
54
- initializeSymbols() {
55
- return {
56
- right: "\uE0B0",
57
- branch: "\uE0A0",
58
- model: "\u26A1",
59
- git_clean: "\u2713",
60
- git_dirty: "\u25CF",
61
- git_conflicts: "\u26A0",
62
- git_ahead: "\u2191",
63
- git_behind: "\u2193",
64
- session_cost: "Session",
65
- daily_cost: "Today"
66
- };
72
+ async getUsageInfo(sessionId) {
73
+ const originalLevel = logger.level;
74
+ logger.level = 0;
75
+ try {
76
+ const claudePaths = getClaudePaths();
77
+ if (claudePaths.length === 0) {
78
+ return {
79
+ session: { cost: null, tokens: null, tokenBreakdown: null },
80
+ daily: { cost: 0, tokens: 0, tokenBreakdown: null }
81
+ };
82
+ }
83
+ const [sessionData, dailyData] = await Promise.all([
84
+ this.getSessionData(sessionId),
85
+ this.getDailyData()
86
+ ]);
87
+ return {
88
+ session: sessionData,
89
+ daily: dailyData
90
+ };
91
+ } catch {
92
+ return {
93
+ session: { cost: null, tokens: null, tokenBreakdown: null },
94
+ daily: { cost: 0, tokens: 0, tokenBreakdown: null }
95
+ };
96
+ } finally {
97
+ logger.level = originalLevel;
98
+ }
67
99
  }
68
- extractBgColor(ansiCode) {
69
- const match = ansiCode.match(/48;2;(\d+);(\d+);(\d+)/);
70
- if (match) {
71
- return `\x1B[38;2;${match[1]};${match[2]};${match[3]}m`;
100
+ async getSessionData(sessionId) {
101
+ try {
102
+ const sessionData = await loadSessionUsageById(sessionId, {
103
+ mode: "auto"
104
+ });
105
+ if (!sessionData) {
106
+ return { cost: null, tokens: null, tokenBreakdown: null };
107
+ }
108
+ const breakdown = sessionData.entries.reduce(
109
+ (acc, entry) => {
110
+ const usage = entry.message.usage;
111
+ return {
112
+ input: acc.input + usage.input_tokens,
113
+ output: acc.output + usage.output_tokens,
114
+ cacheCreation: acc.cacheCreation + (usage.cache_creation_input_tokens || 0),
115
+ cacheRead: acc.cacheRead + (usage.cache_read_input_tokens || 0)
116
+ };
117
+ },
118
+ { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 }
119
+ );
120
+ const totalTokens = breakdown.input + breakdown.output + breakdown.cacheCreation + breakdown.cacheRead;
121
+ return {
122
+ cost: sessionData.totalCost,
123
+ tokens: totalTokens,
124
+ tokenBreakdown: breakdown
125
+ };
126
+ } catch {
127
+ return { cost: null, tokens: null, tokenBreakdown: null };
72
128
  }
73
- return ansiCode.replace("48", "38");
74
129
  }
75
- renderSegment(bgColor, fgColor, text, nextBgColor) {
76
- let output = `${bgColor}${fgColor} ${text} `;
77
- if (nextBgColor) {
78
- const arrowFgColor = this.extractBgColor(bgColor);
79
- output += `${nextBgColor}${arrowFgColor}${this.symbols.right}`;
80
- } else {
81
- const arrowFgColor = this.extractBgColor(bgColor);
82
- output += `${this.colors.colors.reset}${arrowFgColor}${this.symbols.right}${this.colors.colors.reset}`;
130
+ async getDailyData() {
131
+ try {
132
+ const today = /* @__PURE__ */ new Date();
133
+ const todayStr = today.toISOString().split("T")[0]?.replace(/-/g, "") ?? "";
134
+ const dailyData = await loadDailyUsageData({
135
+ since: todayStr,
136
+ until: todayStr,
137
+ mode: "auto"
138
+ });
139
+ if (dailyData.length === 0) {
140
+ return { cost: 0, tokens: 0, tokenBreakdown: null };
141
+ }
142
+ const totals = calculateTotals(dailyData);
143
+ const breakdown = dailyData.reduce(
144
+ (acc, entry) => {
145
+ return {
146
+ input: acc.input + (entry.inputTokens || 0),
147
+ output: acc.output + (entry.outputTokens || 0),
148
+ cacheCreation: acc.cacheCreation + (entry.cacheCreationTokens || 0),
149
+ cacheRead: acc.cacheRead + (entry.cacheReadTokens || 0)
150
+ };
151
+ },
152
+ { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 }
153
+ );
154
+ return {
155
+ cost: totals.totalCost,
156
+ tokens: getTotalTokens(totals),
157
+ tokenBreakdown: breakdown
158
+ };
159
+ } catch {
160
+ return { cost: 0, tokens: 0, tokenBreakdown: null };
83
161
  }
84
- return output;
85
162
  }
86
- sanitizePath(path2) {
87
- return path2.replace(/[;&|`$(){}[\]<>'"\\]/g, "");
163
+ };
164
+
165
+ // src/lib/git-service.ts
166
+ import { execSync } from "child_process";
167
+ var GitService = class {
168
+ sanitizePath(path3) {
169
+ return path3.replace(/[;&|`$(){}[\]<>'"\\]/g, "");
88
170
  }
89
- getGitInfo(workingDir) {
171
+ getGitInfo(workingDir, showSha = false) {
90
172
  try {
91
173
  const sanitizedDir = this.sanitizePath(workingDir);
92
- const branch = execSync("git branch --show-current 2>/dev/null", {
93
- cwd: sanitizedDir,
174
+ const branch = this.getBranch(sanitizedDir);
175
+ const status = this.getStatus(sanitizedDir);
176
+ const { ahead, behind } = this.getAheadBehind(sanitizedDir);
177
+ const sha = showSha ? this.getSha(sanitizedDir) || void 0 : void 0;
178
+ return {
179
+ branch: branch || "detached",
180
+ status,
181
+ ahead,
182
+ behind,
183
+ sha
184
+ };
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+ getBranch(workingDir) {
190
+ try {
191
+ return execSync("git branch --show-current 2>/dev/null", {
192
+ cwd: workingDir,
94
193
  encoding: "utf8",
95
194
  timeout: 1e3
96
- }).trim();
195
+ }).trim() || null;
196
+ } catch {
197
+ return null;
198
+ }
199
+ }
200
+ getStatus(workingDir) {
201
+ try {
97
202
  const gitStatus = execSync("git status --porcelain 2>/dev/null", {
98
- cwd: sanitizedDir,
203
+ cwd: workingDir,
99
204
  encoding: "utf8",
100
205
  timeout: 1e3
101
206
  }).trim();
102
- let status = "clean";
103
- if (gitStatus) {
104
- if (gitStatus.includes("UU") || gitStatus.includes("AA") || gitStatus.includes("DD")) {
105
- status = "conflicts";
106
- } else {
107
- status = "dirty";
108
- }
109
- }
110
- let ahead = 0, behind = 0;
111
- try {
112
- const aheadResult = execSync(
113
- "git rev-list --count @{u}..HEAD 2>/dev/null",
114
- {
115
- cwd: sanitizedDir,
116
- encoding: "utf8",
117
- timeout: 1e3
118
- }
119
- ).trim();
120
- ahead = parseInt(aheadResult) || 0;
121
- const behindResult = execSync(
122
- "git rev-list --count HEAD..@{u} 2>/dev/null",
123
- {
124
- cwd: sanitizedDir,
125
- encoding: "utf8",
126
- timeout: 1e3
127
- }
128
- ).trim();
129
- behind = parseInt(behindResult) || 0;
130
- } catch {
207
+ if (!gitStatus) return "clean";
208
+ if (gitStatus.includes("UU") || gitStatus.includes("AA") || gitStatus.includes("DD")) {
209
+ return "conflicts";
131
210
  }
132
- return { branch: branch || "detached", status, ahead, behind };
211
+ return "dirty";
133
212
  } catch {
134
- return null;
213
+ return "clean";
135
214
  }
136
215
  }
137
- async getCostInfo(sessionId) {
138
- const originalLevel = logger.level;
139
- logger.level = 0;
216
+ getAheadBehind(workingDir) {
140
217
  try {
141
- const claudePaths = getClaudePaths();
142
- if (claudePaths.length === 0) {
143
- logger.level = originalLevel;
144
- return { sessionCost: null, dailyCost: 0 };
145
- }
146
- let sessionCost = null;
147
- try {
148
- const sessionData = await loadSessionUsageById(sessionId, {
149
- mode: "auto"
150
- });
151
- if (sessionData != null) {
152
- sessionCost = sessionData.totalCost;
218
+ const aheadResult = execSync(
219
+ "git rev-list --count @{u}..HEAD 2>/dev/null",
220
+ {
221
+ cwd: workingDir,
222
+ encoding: "utf8",
223
+ timeout: 1e3
153
224
  }
154
- } catch {
155
- }
156
- let dailyCost = 0;
157
- try {
158
- const today = /* @__PURE__ */ new Date();
159
- const todayStr = today.toISOString().split("T")[0]?.replace(/-/g, "") ?? "";
160
- const dailyData = await loadDailyUsageData({
161
- since: todayStr,
162
- until: todayStr,
163
- mode: "auto"
164
- });
165
- if (dailyData.length > 0) {
166
- const totals = calculateTotals(dailyData);
167
- dailyCost = totals.totalCost;
225
+ ).trim();
226
+ const behindResult = execSync(
227
+ "git rev-list --count HEAD..@{u} 2>/dev/null",
228
+ {
229
+ cwd: workingDir,
230
+ encoding: "utf8",
231
+ timeout: 1e3
168
232
  }
169
- } catch {
233
+ ).trim();
234
+ return {
235
+ ahead: parseInt(aheadResult) || 0,
236
+ behind: parseInt(behindResult) || 0
237
+ };
238
+ } catch {
239
+ return { ahead: 0, behind: 0 };
240
+ }
241
+ }
242
+ getSha(workingDir) {
243
+ try {
244
+ const sha = execSync("git rev-parse --short=7 HEAD 2>/dev/null", {
245
+ cwd: workingDir,
246
+ encoding: "utf8",
247
+ timeout: 1e3
248
+ }).trim();
249
+ return sha || null;
250
+ } catch {
251
+ return null;
252
+ }
253
+ }
254
+ };
255
+
256
+ // src/lib/tmux-service.ts
257
+ import { execSync as execSync2 } from "child_process";
258
+ var TmuxService = class {
259
+ getSessionId() {
260
+ try {
261
+ if (!process.env.TMUX_PANE) {
262
+ return null;
170
263
  }
171
- logger.level = originalLevel;
172
- return { sessionCost, dailyCost };
264
+ const sessionId = execSync2("tmux display-message -p '#S'", {
265
+ encoding: "utf8",
266
+ timeout: 1e3
267
+ }).trim();
268
+ return sessionId || null;
173
269
  } catch {
174
- logger.level = originalLevel;
175
- return { sessionCost: null, dailyCost: 0 };
270
+ return null;
176
271
  }
177
272
  }
178
- formatCost(cost) {
179
- if (cost === null) return "N/A";
180
- if (cost < 0.01) return "<$0.01";
181
- return `$${cost.toFixed(2)}`;
273
+ isInTmux() {
274
+ return !!process.env.TMUX_PANE;
182
275
  }
183
- async generateStatusline(hookData, style = "colors") {
184
- const modelName = hookData.model?.display_name || "Claude";
276
+ };
277
+
278
+ // src/lib/formatters.ts
279
+ function formatCost(cost) {
280
+ if (cost === null) return "N/A";
281
+ if (cost < 0.01) return "<$0.01";
282
+ return `$${cost.toFixed(2)}`;
283
+ }
284
+ function formatTokens(tokens) {
285
+ if (tokens === null) return "N/A";
286
+ if (tokens === 0) return "0 tokens";
287
+ if (tokens >= 1e6) {
288
+ return `${(tokens / 1e6).toFixed(1)}M tokens`;
289
+ } else if (tokens >= 1e3) {
290
+ return `${(tokens / 1e3).toFixed(1)}K tokens`;
291
+ }
292
+ return `${tokens} tokens`;
293
+ }
294
+ function formatTokenBreakdown(breakdown) {
295
+ if (!breakdown) return "N/A";
296
+ const parts = [];
297
+ if (breakdown.input > 0) {
298
+ parts.push(`${formatTokens(breakdown.input).replace(" tokens", "")}in`);
299
+ }
300
+ if (breakdown.output > 0) {
301
+ parts.push(`${formatTokens(breakdown.output).replace(" tokens", "")}out`);
302
+ }
303
+ if (breakdown.cacheCreation > 0 || breakdown.cacheRead > 0) {
304
+ const totalCached = breakdown.cacheCreation + breakdown.cacheRead;
305
+ parts.push(`${formatTokens(totalCached).replace(" tokens", "")}cached`);
306
+ }
307
+ return parts.length > 0 ? parts.join(" + ") : "0 tokens";
308
+ }
309
+ function formatTimeRemaining(minutes) {
310
+ if (minutes <= 0) return "0m";
311
+ const hours = Math.floor(minutes / 60);
312
+ const mins = minutes % 60;
313
+ if (hours > 0) {
314
+ return `${hours}h${mins > 0 ? ` ${mins}m` : ""}`;
315
+ }
316
+ return `${mins}m`;
317
+ }
318
+
319
+ // src/lib/budget.ts
320
+ function calculateBudgetPercentage(cost, budget) {
321
+ if (!budget || budget <= 0 || cost < 0) return null;
322
+ return Math.min(100, cost / budget * 100);
323
+ }
324
+ function getBudgetStatus(cost, budget, warningThreshold = 80) {
325
+ const percentage = calculateBudgetPercentage(cost, budget);
326
+ if (percentage === null) {
327
+ return {
328
+ percentage: null,
329
+ isWarning: false,
330
+ displayText: ""
331
+ };
332
+ }
333
+ const percentStr = `${percentage.toFixed(0)}%`;
334
+ const isWarning = percentage >= warningThreshold;
335
+ let displayText = "";
336
+ if (isWarning) {
337
+ displayText = ` !${percentStr}`;
338
+ } else if (percentage >= 50) {
339
+ displayText = ` +${percentStr}`;
340
+ } else {
341
+ displayText = ` ${percentStr}`;
342
+ }
343
+ return {
344
+ percentage,
345
+ isWarning,
346
+ displayText
347
+ };
348
+ }
349
+
350
+ // src/lib/segment-renderer.ts
351
+ var SegmentRenderer = class {
352
+ constructor(config, symbols) {
353
+ this.config = config;
354
+ this.symbols = symbols;
355
+ }
356
+ renderDirectory(hookData, colors) {
185
357
  const currentDir = hookData.workspace?.current_dir || hookData.cwd || "/";
186
358
  const projectDir = hookData.workspace?.project_dir;
187
- let dirName;
359
+ const dirName = this.getDisplayDirectoryName(currentDir, projectDir);
360
+ return {
361
+ text: dirName,
362
+ bgColor: colors.modeBg,
363
+ fgColor: colors.modeFg
364
+ };
365
+ }
366
+ renderGit(gitInfo, colors, showSha = false) {
367
+ if (!gitInfo) return null;
368
+ let gitStatusIcon = this.symbols.git_clean;
369
+ if (gitInfo.status === "conflicts") {
370
+ gitStatusIcon = this.symbols.git_conflicts;
371
+ } else if (gitInfo.status === "dirty") {
372
+ gitStatusIcon = this.symbols.git_dirty;
373
+ }
374
+ let text = `${this.symbols.branch} ${gitInfo.branch} ${gitStatusIcon}`;
375
+ if (gitInfo.sha && showSha) {
376
+ text += ` ${gitInfo.sha}`;
377
+ }
378
+ if (gitInfo.ahead > 0 && gitInfo.behind > 0) {
379
+ text += ` ${this.symbols.git_ahead}${gitInfo.ahead}${this.symbols.git_behind}${gitInfo.behind}`;
380
+ } else if (gitInfo.ahead > 0) {
381
+ text += ` ${this.symbols.git_ahead}${gitInfo.ahead}`;
382
+ } else if (gitInfo.behind > 0) {
383
+ text += ` ${this.symbols.git_behind}${gitInfo.behind}`;
384
+ }
385
+ return {
386
+ text,
387
+ bgColor: colors.gitBg,
388
+ fgColor: colors.gitFg
389
+ };
390
+ }
391
+ renderModel(hookData, colors) {
392
+ const modelName = hookData.model?.display_name || "Claude";
393
+ return {
394
+ text: `${this.symbols.model} ${modelName}`,
395
+ bgColor: colors.todayBg,
396
+ fgColor: colors.todayFg
397
+ };
398
+ }
399
+ renderSession(usageInfo, colors, type = "cost") {
400
+ const sessionBudget = this.config.budget?.session;
401
+ const text = `${this.symbols.session_cost} ${this.formatUsageWithBudget(
402
+ usageInfo.session.cost,
403
+ usageInfo.session.tokens,
404
+ usageInfo.session.tokenBreakdown,
405
+ type,
406
+ sessionBudget?.amount,
407
+ sessionBudget?.warningThreshold || 80
408
+ )}`;
409
+ return {
410
+ text,
411
+ bgColor: colors.sessionBg,
412
+ fgColor: colors.sessionFg
413
+ };
414
+ }
415
+ renderToday(usageInfo, colors, type = "cost") {
416
+ const todayBudget = this.config.budget?.today;
417
+ const text = `Today ${this.formatUsageWithBudget(
418
+ usageInfo.daily.cost,
419
+ usageInfo.daily.tokens,
420
+ usageInfo.daily.tokenBreakdown,
421
+ type,
422
+ todayBudget?.amount,
423
+ todayBudget?.warningThreshold || 80
424
+ )}`;
425
+ return {
426
+ text,
427
+ bgColor: colors.burnLowBg,
428
+ fgColor: colors.burnFg
429
+ };
430
+ }
431
+ renderBlock(blockInfo, colors, type = "cost") {
432
+ if (!blockInfo) return null;
433
+ const text = `${this.symbols.block_cost} ${this.formatSessionBlockInfo(blockInfo, type)}`;
434
+ return {
435
+ text,
436
+ bgColor: colors.blockBg,
437
+ fgColor: colors.blockFg
438
+ };
439
+ }
440
+ renderTmux(sessionId, colors) {
441
+ if (!sessionId) return null;
442
+ return {
443
+ text: `tmux:${sessionId}`,
444
+ bgColor: colors.tmuxBg,
445
+ fgColor: colors.tmuxFg
446
+ };
447
+ }
448
+ getDisplayDirectoryName(currentDir, projectDir) {
188
449
  if (projectDir && projectDir !== currentDir) {
189
450
  const projectName = projectDir.split("/").pop() || "project";
190
451
  const currentDirName = currentDir.split("/").pop() || "root";
191
- dirName = currentDir.includes(projectDir) ? `${projectName}/${currentDirName}` : currentDirName;
192
- } else {
193
- dirName = currentDir.split("/").pop() || "root";
194
- }
195
- const gitInfo = this.getGitInfo(currentDir);
196
- const sessionId = hookData.session_id;
197
- const costInfo = await this.getCostInfo(sessionId);
198
- const colors = this.colors[style];
199
- let statusline = "";
200
- statusline += this.renderSegment(
201
- colors.modeBg,
202
- colors.modeFg,
203
- dirName,
204
- gitInfo ? colors.sessionBg : colors.dailyBg
452
+ if (currentDir.includes(projectDir)) {
453
+ return `${projectName}/${currentDirName}`;
454
+ }
455
+ return currentDirName;
456
+ }
457
+ return currentDir.split("/").pop() || "root";
458
+ }
459
+ formatUsageDisplay(cost, tokens, tokenBreakdown, type) {
460
+ switch (type) {
461
+ case "cost":
462
+ return formatCost(cost);
463
+ case "tokens":
464
+ return formatTokens(tokens);
465
+ case "both":
466
+ return `${formatCost(cost)} (${formatTokens(tokens)})`;
467
+ case "breakdown":
468
+ return formatTokenBreakdown(tokenBreakdown);
469
+ default:
470
+ return formatCost(cost);
471
+ }
472
+ }
473
+ formatUsageWithBudget(cost, tokens, tokenBreakdown, type, budget, warningThreshold = 80) {
474
+ const baseDisplay = this.formatUsageDisplay(
475
+ cost,
476
+ tokens,
477
+ tokenBreakdown,
478
+ type
205
479
  );
206
- if (gitInfo) {
207
- let gitStatusIcon = this.symbols.git_clean;
208
- if (gitInfo.status === "conflicts") {
209
- gitStatusIcon = this.symbols.git_conflicts;
210
- } else if (gitInfo.status === "dirty") {
211
- gitStatusIcon = this.symbols.git_dirty;
480
+ if (budget && budget > 0 && cost !== null) {
481
+ const budgetStatus = getBudgetStatus(cost, budget, warningThreshold);
482
+ return baseDisplay + budgetStatus.displayText;
483
+ }
484
+ return baseDisplay;
485
+ }
486
+ formatSessionBlockInfo(blockInfo, type = "cost") {
487
+ if (!blockInfo.isActive) {
488
+ return "No active block";
489
+ }
490
+ const timeStr = formatTimeRemaining(blockInfo.timeRemaining);
491
+ if (type === "tokens") {
492
+ const tokensStr = formatTokens(blockInfo.tokens);
493
+ let result = `${tokensStr} (${timeStr} left)`;
494
+ if (blockInfo.tokenBurnRate !== null && blockInfo.tokenBurnRate > 0) {
495
+ const burnRateStr = `${formatTokens(blockInfo.tokenBurnRate)}/hr`;
496
+ result += ` ${burnRateStr}`;
212
497
  }
213
- let branchText = `${this.symbols.branch} ${gitInfo.branch} ${gitStatusIcon}`;
214
- if (gitInfo.ahead > 0 && gitInfo.behind > 0) {
215
- branchText += ` ${this.symbols.git_ahead}${gitInfo.ahead}${this.symbols.git_behind}${gitInfo.behind}`;
216
- } else if (gitInfo.ahead > 0) {
217
- branchText += ` ${this.symbols.git_ahead}${gitInfo.ahead}`;
218
- } else if (gitInfo.behind > 0) {
219
- branchText += ` ${this.symbols.git_behind}${gitInfo.behind}`;
498
+ return result;
499
+ } else {
500
+ const costStr = formatCost(blockInfo.cost);
501
+ let result = `${costStr} (${timeStr} left)`;
502
+ if (blockInfo.burnRate !== null && blockInfo.burnRate > 0) {
503
+ const burnRateStr = `${formatCost(blockInfo.burnRate)}/hr`;
504
+ result += ` ${burnRateStr}`;
220
505
  }
221
- statusline += this.renderSegment(
222
- colors.sessionBg,
223
- colors.sessionFg,
224
- branchText,
225
- colors.dailyBg
226
- );
506
+ return result;
227
507
  }
228
- statusline += this.renderSegment(
229
- colors.dailyBg,
230
- colors.dailyFg,
231
- `${this.symbols.model} ${modelName}`,
232
- colors.blockBg
233
- );
234
- statusline += this.renderSegment(
235
- colors.blockBg,
236
- colors.blockFg,
237
- `${this.symbols.session_cost} ${this.formatCost(costInfo.sessionCost)}`,
238
- colors.burnLowBg
508
+ }
509
+ };
510
+
511
+ // src/powerline.ts
512
+ var PowerlineRenderer = class {
513
+ constructor(config) {
514
+ this.config = config;
515
+ this.symbols = this.initializeSymbols();
516
+ this.usageProvider = new UsageProvider();
517
+ this.gitService = new GitService();
518
+ this.tmuxService = new TmuxService();
519
+ this.segmentRenderer = new SegmentRenderer(config, this.symbols);
520
+ }
521
+ symbols;
522
+ usageProvider;
523
+ gitService;
524
+ tmuxService;
525
+ segmentRenderer;
526
+ async generateStatusline(hookData) {
527
+ const usageInfo = await this.usageProvider.getUsageInfo(
528
+ hookData.session_id
239
529
  );
240
- statusline += this.renderSegment(
241
- colors.burnLowBg,
242
- colors.burnFg,
243
- `${this.symbols.daily_cost} ${this.formatCost(costInfo.dailyCost)}`
530
+ let sessionBlockInfo = null;
531
+ if (this.needsSessionBlock()) {
532
+ sessionBlockInfo = await this.usageProvider.getSessionBlockInfo();
533
+ }
534
+ const lines = this.config.display.lines.map(
535
+ (lineConfig) => this.renderLine(lineConfig, hookData, usageInfo, sessionBlockInfo)
536
+ ).filter((line) => line.length > 0);
537
+ return lines.join("\n");
538
+ }
539
+ needsSessionBlock() {
540
+ return this.config.display.lines.some(
541
+ (line) => line.segments.block?.enabled
244
542
  );
245
- return statusline;
543
+ }
544
+ renderLine(lineConfig, hookData, usageInfo, sessionBlockInfo) {
545
+ const colors = this.getThemeColors();
546
+ const currentDir = hookData.workspace?.current_dir || hookData.cwd || "/";
547
+ const segments = Object.entries(lineConfig.segments).filter(([_, config]) => config?.enabled).map(([type, config]) => ({ type, config }));
548
+ let line = "";
549
+ for (let i = 0; i < segments.length; i++) {
550
+ const segment = segments[i];
551
+ if (!segment) continue;
552
+ const isLast = i === segments.length - 1;
553
+ const nextSegment = !isLast ? segments[i + 1] : null;
554
+ const nextBgColor = nextSegment ? this.getSegmentBgColor(nextSegment.type, colors) : "";
555
+ const segmentData = this.renderSegment(
556
+ segment,
557
+ hookData,
558
+ usageInfo,
559
+ sessionBlockInfo,
560
+ colors,
561
+ currentDir
562
+ );
563
+ if (segmentData) {
564
+ line += this.formatSegment(
565
+ segmentData.bgColor,
566
+ segmentData.fgColor,
567
+ segmentData.text,
568
+ isLast ? void 0 : nextBgColor
569
+ );
570
+ }
571
+ }
572
+ return line;
573
+ }
574
+ renderSegment(segment, hookData, usageInfo, sessionBlockInfo, colors, currentDir) {
575
+ switch (segment.type) {
576
+ case "directory":
577
+ return this.segmentRenderer.renderDirectory(hookData, colors);
578
+ case "git":
579
+ const showSha = segment.config?.showSha || false;
580
+ const gitInfo = this.gitService.getGitInfo(currentDir, showSha);
581
+ return gitInfo ? this.segmentRenderer.renderGit(gitInfo, colors, showSha) : null;
582
+ case "model":
583
+ return this.segmentRenderer.renderModel(hookData, colors);
584
+ case "session":
585
+ const usageType = segment.config?.type || "cost";
586
+ return this.segmentRenderer.renderSession(usageInfo, colors, usageType);
587
+ case "today":
588
+ const todayType = segment.config?.type || "cost";
589
+ return this.segmentRenderer.renderToday(usageInfo, colors, todayType);
590
+ case "block":
591
+ const blockType = segment.config?.type || "cost";
592
+ return this.segmentRenderer.renderBlock(
593
+ sessionBlockInfo,
594
+ colors,
595
+ blockType
596
+ );
597
+ case "tmux":
598
+ const tmuxSessionId = this.tmuxService.getSessionId();
599
+ return this.segmentRenderer.renderTmux(tmuxSessionId, colors);
600
+ default:
601
+ return null;
602
+ }
603
+ }
604
+ initializeSymbols() {
605
+ return {
606
+ right: "\uE0B0",
607
+ branch: "\uE0A0",
608
+ model: "\u26A1",
609
+ git_clean: "\u2713",
610
+ git_dirty: "\u25CF",
611
+ git_conflicts: "\u26A0",
612
+ git_ahead: "\u2191",
613
+ git_behind: "\u2193",
614
+ session_cost: "Session",
615
+ daily_cost: "Today",
616
+ block_cost: "Block"
617
+ };
618
+ }
619
+ getThemeColors() {
620
+ const theme = this.config.theme;
621
+ const colorTheme = this.config.colors[theme];
622
+ if (!colorTheme) {
623
+ throw new Error(`Theme '${theme}' not found in configuration`);
624
+ }
625
+ return {
626
+ reset: "\x1B[0m",
627
+ modeBg: hexToAnsi(colorTheme.directory.bg, true),
628
+ modeFg: hexToAnsi(colorTheme.directory.fg, false),
629
+ gitBg: hexToAnsi(colorTheme.git.bg, true),
630
+ gitFg: hexToAnsi(colorTheme.git.fg, false),
631
+ sessionBg: hexToAnsi(colorTheme.session.bg, true),
632
+ sessionFg: hexToAnsi(colorTheme.session.fg, false),
633
+ todayBg: hexToAnsi(colorTheme.today.bg, true),
634
+ todayFg: hexToAnsi(colorTheme.today.fg, false),
635
+ blockBg: hexToAnsi(colorTheme.block.bg, true),
636
+ blockFg: hexToAnsi(colorTheme.block.fg, false),
637
+ burnLowBg: hexToAnsi(colorTheme.today.bg, true),
638
+ burnFg: hexToAnsi(colorTheme.today.fg, false),
639
+ tmuxBg: hexToAnsi(colorTheme.tmux.bg, true),
640
+ tmuxFg: hexToAnsi(colorTheme.tmux.fg, false)
641
+ };
642
+ }
643
+ getSegmentBgColor(segmentType, colors) {
644
+ switch (segmentType) {
645
+ case "directory":
646
+ return colors.modeBg;
647
+ case "git":
648
+ return colors.gitBg;
649
+ case "model":
650
+ return colors.todayBg;
651
+ case "session":
652
+ return colors.sessionBg;
653
+ case "today":
654
+ return colors.burnLowBg;
655
+ case "block":
656
+ return colors.blockBg;
657
+ case "tmux":
658
+ return colors.tmuxBg;
659
+ default:
660
+ return colors.modeBg;
661
+ }
662
+ }
663
+ formatSegment(bgColor, fgColor, text, nextBgColor) {
664
+ let output = `${bgColor}${fgColor} ${text} `;
665
+ if (nextBgColor) {
666
+ const arrowFgColor = extractBgToFg(bgColor);
667
+ output += `${nextBgColor}${arrowFgColor}${this.symbols.right}`;
668
+ } else {
669
+ const arrowFgColor = extractBgToFg(bgColor);
670
+ output += `\x1B[0m${arrowFgColor}${this.symbols.right}\x1B[0m`;
671
+ }
672
+ return output;
246
673
  }
247
674
  };
248
675
 
676
+ // src/config/loader.ts
677
+ import fs from "fs";
678
+ import path from "path";
679
+ import os from "os";
680
+
681
+ // src/config/defaults.ts
682
+ var DEFAULT_CONFIG = {
683
+ theme: "dark",
684
+ display: {
685
+ lines: [
686
+ {
687
+ segments: {
688
+ directory: { enabled: true },
689
+ git: {
690
+ enabled: true,
691
+ showSha: false
692
+ },
693
+ model: { enabled: true },
694
+ session: { enabled: true, type: "tokens" },
695
+ today: { enabled: true, type: "both" },
696
+ block: { enabled: false, type: "cost" },
697
+ tmux: { enabled: false }
698
+ }
699
+ }
700
+ ]
701
+ },
702
+ colors: {
703
+ light: {
704
+ directory: { bg: "#ff6b47", fg: "#ffffff" },
705
+ git: { bg: "#4fb3d9", fg: "#ffffff" },
706
+ model: { bg: "#87ceeb", fg: "#000000" },
707
+ session: { bg: "#da70d6", fg: "#ffffff" },
708
+ today: { bg: "#90ee90", fg: "#ffffff" },
709
+ block: { bg: "#ff8c00", fg: "#ffffff" },
710
+ tmux: { bg: "#32cd32", fg: "#ffffff" }
711
+ },
712
+ dark: {
713
+ directory: { bg: "#8b4513", fg: "#ffffff" },
714
+ git: { bg: "#404040", fg: "#ffffff" },
715
+ model: { bg: "#2d2d2d", fg: "#ffffff" },
716
+ session: { bg: "#202020", fg: "#00ffff" },
717
+ today: { bg: "#1c1c1c", fg: "#ffffff" },
718
+ block: { bg: "#8b4500", fg: "#ffffff" },
719
+ tmux: { bg: "#2f4f2f", fg: "#90ee90" }
720
+ },
721
+ custom: {
722
+ directory: { bg: "#ff6600", fg: "#ffffff" },
723
+ git: { bg: "#0066cc", fg: "#ffffff" },
724
+ model: { bg: "#9900cc", fg: "#ffffff" },
725
+ session: { bg: "#cc0099", fg: "#ffffff" },
726
+ today: { bg: "#00cc66", fg: "#000000" },
727
+ block: { bg: "#cc6600", fg: "#ffffff" },
728
+ tmux: { bg: "#228b22", fg: "#ffffff" }
729
+ }
730
+ },
731
+ budget: {
732
+ session: {
733
+ warningThreshold: 80
734
+ },
735
+ today: {
736
+ amount: 50,
737
+ warningThreshold: 80
738
+ }
739
+ }
740
+ };
741
+
742
+ // src/config/loader.ts
743
+ function deepMerge(target, source) {
744
+ const result = { ...target };
745
+ for (const key in source) {
746
+ if (source[key] !== void 0) {
747
+ if (typeof source[key] === "object" && source[key] !== null && !Array.isArray(source[key])) {
748
+ result[key] = deepMerge(result[key] || {}, source[key]);
749
+ } else {
750
+ result[key] = source[key];
751
+ }
752
+ }
753
+ }
754
+ return result;
755
+ }
756
+ function findConfigFile(customPath, projectDir) {
757
+ if (customPath) {
758
+ return fs.existsSync(customPath) ? customPath : null;
759
+ }
760
+ const locations = [
761
+ ...projectDir ? [path.join(projectDir, ".claude-powerline.json")] : [],
762
+ path.join(process.cwd(), ".claude-powerline.json"),
763
+ path.join(os.homedir(), ".claude", "claude-powerline.json"),
764
+ path.join(os.homedir(), ".config", "claude-powerline", "config.json")
765
+ ];
766
+ return locations.find(fs.existsSync) || null;
767
+ }
768
+ function loadConfigFile(filePath) {
769
+ try {
770
+ const content = fs.readFileSync(filePath, "utf-8");
771
+ return JSON.parse(content);
772
+ } catch (error) {
773
+ throw new Error(
774
+ `Failed to load config file ${filePath}: ${error instanceof Error ? error.message : String(error)}`
775
+ );
776
+ }
777
+ }
778
+ function loadEnvConfig() {
779
+ const config = {};
780
+ if (process.env.CLAUDE_POWERLINE_THEME) {
781
+ config.theme = process.env.CLAUDE_POWERLINE_THEME;
782
+ }
783
+ if (process.env.CLAUDE_POWERLINE_USAGE_TYPE) {
784
+ const usageType = process.env.CLAUDE_POWERLINE_USAGE_TYPE;
785
+ config.display = config.display || { lines: [] };
786
+ if (config.display.lines.length === 0) {
787
+ config.display.lines = [{ segments: {} }];
788
+ }
789
+ config.display.lines.forEach((line) => {
790
+ if (line.segments.session) {
791
+ line.segments.session.type = usageType;
792
+ }
793
+ if (line.segments.today) {
794
+ line.segments.today.type = usageType;
795
+ }
796
+ if (line.segments.block) {
797
+ line.segments.block.type = usageType === "breakdown" || usageType === "both" ? "cost" : usageType;
798
+ }
799
+ });
800
+ }
801
+ return config;
802
+ }
803
+ function getConfigPathFromEnv() {
804
+ return process.env.CLAUDE_POWERLINE_CONFIG;
805
+ }
806
+ function parseCLIOverrides(args) {
807
+ const config = {};
808
+ const themeIndex = args.findIndex((arg) => arg.startsWith("--theme="));
809
+ if (themeIndex !== -1) {
810
+ const theme = args[themeIndex]?.split("=")[1];
811
+ if (theme) {
812
+ config.theme = theme;
813
+ }
814
+ }
815
+ const dailyBudgetIndex = args.findIndex(
816
+ (arg) => arg.startsWith("--daily-budget=")
817
+ );
818
+ if (dailyBudgetIndex !== -1) {
819
+ const dailyBudget = parseFloat(args[dailyBudgetIndex]?.split("=")[1] || "");
820
+ if (!isNaN(dailyBudget) && dailyBudget > 0) {
821
+ config.budget = {
822
+ ...config.budget,
823
+ today: {
824
+ ...DEFAULT_CONFIG.budget?.today,
825
+ amount: dailyBudget
826
+ }
827
+ };
828
+ }
829
+ }
830
+ const sessionBudgetIndex = args.findIndex(
831
+ (arg) => arg.startsWith("--session-budget=")
832
+ );
833
+ if (sessionBudgetIndex !== -1) {
834
+ const sessionBudget = parseFloat(
835
+ args[sessionBudgetIndex]?.split("=")[1] || ""
836
+ );
837
+ if (!isNaN(sessionBudget) && sessionBudget > 0) {
838
+ config.budget = {
839
+ ...config.budget,
840
+ session: {
841
+ ...DEFAULT_CONFIG.budget?.session,
842
+ amount: sessionBudget
843
+ }
844
+ };
845
+ }
846
+ }
847
+ return config;
848
+ }
849
+ function loadConfig(options = {}) {
850
+ const {
851
+ configPath,
852
+ ignoreEnvVars = false,
853
+ cliOverrides = {},
854
+ projectDir
855
+ } = options;
856
+ let config = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
857
+ const configFile = findConfigFile(configPath, projectDir);
858
+ if (configFile) {
859
+ try {
860
+ const fileConfig = loadConfigFile(configFile);
861
+ config = deepMerge(config, fileConfig);
862
+ } catch (err) {
863
+ console.warn(
864
+ `Warning: ${err instanceof Error ? err.message : String(err)}`
865
+ );
866
+ }
867
+ }
868
+ if (!ignoreEnvVars) {
869
+ const envConfig = loadEnvConfig();
870
+ config = deepMerge(config, envConfig);
871
+ }
872
+ config = deepMerge(config, cliOverrides);
873
+ return config;
874
+ }
875
+ function loadConfigFromCLI(args = process.argv, projectDir) {
876
+ const configPathIndex = args.findIndex((arg) => arg.startsWith("--config="));
877
+ const configPath = configPathIndex !== -1 ? args[configPathIndex]?.split("=")[1] : getConfigPathFromEnv();
878
+ const cliOverrides = parseCLIOverrides(args);
879
+ return loadConfig({ configPath, cliOverrides, projectDir });
880
+ }
881
+ function getDefaultConfigJSON() {
882
+ return JSON.stringify(DEFAULT_CONFIG, null, 2);
883
+ }
884
+
249
885
  // src/index.ts
250
886
  async function installFonts() {
251
887
  try {
252
- const platform = os.platform();
888
+ const platform = os2.platform();
253
889
  let fontDir;
254
890
  if (platform === "darwin") {
255
- fontDir = path.join(os.homedir(), "Library", "Fonts");
891
+ fontDir = path2.join(os2.homedir(), "Library", "Fonts");
256
892
  } else if (platform === "linux") {
257
- fontDir = path.join(os.homedir(), ".local", "share", "fonts");
893
+ fontDir = path2.join(os2.homedir(), ".local", "share", "fonts");
258
894
  } else if (platform === "win32") {
259
- fontDir = path.join(
260
- os.homedir(),
895
+ fontDir = path2.join(
896
+ os2.homedir(),
261
897
  "AppData",
262
898
  "Local",
263
899
  "Microsoft",
@@ -268,29 +904,29 @@ async function installFonts() {
268
904
  console.log("Unsupported platform for font installation");
269
905
  return;
270
906
  }
271
- if (!fs.existsSync(fontDir)) {
272
- fs.mkdirSync(fontDir, { recursive: true });
907
+ if (!fs2.existsSync(fontDir)) {
908
+ fs2.mkdirSync(fontDir, { recursive: true });
273
909
  }
274
910
  console.log("\u{1F4E6} Installing Powerline Fonts...");
275
911
  console.log("Downloading from https://github.com/powerline/fonts");
276
- const tempDir = path.join(os.tmpdir(), "powerline-fonts");
912
+ const tempDir = path2.join(os2.tmpdir(), "powerline-fonts");
277
913
  try {
278
- if (fs.existsSync(tempDir)) {
279
- fs.rmSync(tempDir, { recursive: true, force: true });
914
+ if (fs2.existsSync(tempDir)) {
915
+ fs2.rmSync(tempDir, { recursive: true, force: true });
280
916
  }
281
917
  console.log("Cloning powerline fonts repository...");
282
- execSync2(
918
+ execSync3(
283
919
  "git clone --depth=1 https://github.com/powerline/fonts.git powerline-fonts",
284
920
  {
285
921
  stdio: "inherit",
286
- cwd: os.tmpdir()
922
+ cwd: os2.tmpdir()
287
923
  }
288
924
  );
289
925
  console.log("Installing fonts...");
290
- const installScript = path.join(tempDir, "install.sh");
291
- if (fs.existsSync(installScript)) {
292
- fs.chmodSync(installScript, 493);
293
- execSync2("./install.sh", { stdio: "inherit", cwd: tempDir });
926
+ const installScript = path2.join(tempDir, "install.sh");
927
+ if (fs2.existsSync(installScript)) {
928
+ fs2.chmodSync(installScript, 493);
929
+ execSync3("./install.sh", { stdio: "inherit", cwd: tempDir });
294
930
  } else {
295
931
  throw new Error(
296
932
  "Install script not found in powerline fonts repository"
@@ -304,8 +940,8 @@ async function installFonts() {
304
940
  "Popular choices: Source Code Pro Powerline, DejaVu Sans Mono Powerline, Ubuntu Mono Powerline"
305
941
  );
306
942
  } finally {
307
- if (fs.existsSync(tempDir)) {
308
- fs.rmSync(tempDir, { recursive: true, force: true });
943
+ if (fs2.existsSync(tempDir)) {
944
+ fs2.rmSync(tempDir, { recursive: true, force: true });
309
945
  }
310
946
  }
311
947
  } catch (error) {
@@ -320,12 +956,16 @@ async function installFonts() {
320
956
  }
321
957
  async function main() {
322
958
  try {
323
- const style = process.argv.includes("--dark") ? "dark" : "colors";
324
- const showHelp = process.argv.includes("--help") || process.argv.includes("-h");
325
- const installFontsFlag = process.argv.includes("--install-fonts");
959
+ const showHelp = process2.argv.includes("--help") || process2.argv.includes("-h");
960
+ const installFontsFlag = process2.argv.includes("--install-fonts");
961
+ const printDefaultConfig = process2.argv.includes("--print-default-config");
326
962
  if (installFontsFlag) {
327
963
  await installFonts();
328
- process.exit(0);
964
+ process2.exit(0);
965
+ }
966
+ if (printDefaultConfig) {
967
+ console.log(getDefaultConfigJSON());
968
+ process2.exit(0);
329
969
  }
330
970
  if (showHelp) {
331
971
  console.log(`
@@ -334,9 +974,26 @@ claude-powerline - Beautiful powerline statusline for Claude Code
334
974
  Usage: claude-powerline [options]
335
975
 
336
976
  Options:
337
- --dark Use dark color scheme
338
- --install-fonts Install powerline fonts to system
339
- -h, --help Show this help
977
+ --theme=THEME Set theme: light, dark, custom
978
+ --usage=TYPE Usage display: cost, tokens, both, breakdown
979
+ --session-budget=AMOUNT Set session budget for percentage tracking
980
+ --daily-budget=AMOUNT Set daily budget for percentage tracking
981
+ --config=PATH Use custom config file path
982
+ --install-fonts Install powerline fonts to system
983
+ --print-default-config Print default configuration template
984
+ -h, --help Show this help
985
+
986
+ Configuration:
987
+ Config files are loaded in this order (highest priority first):
988
+ 1. CLI arguments (--theme, --usage, --config)
989
+ 2. Environment variables (CLAUDE_POWERLINE_THEME, CLAUDE_POWERLINE_USAGE_TYPE, CLAUDE_POWERLINE_CONFIG)
990
+ 3. ./.claude-powerline.json (project)
991
+ 4. ~/.claude/claude-powerline.json (user)
992
+ 5. ~/.config/claude-powerline/config.json (XDG)
993
+
994
+ Creating a config file:
995
+ claude-powerline --print-default-config > ~/.claude/claude-powerline.json
996
+ claude-powerline --print-default-config > .claude-powerline.json
340
997
 
341
998
  Usage in Claude Code settings.json:
342
999
  {
@@ -347,7 +1004,7 @@ Usage in Claude Code settings.json:
347
1004
  }
348
1005
  }
349
1006
  `);
350
- process.exit(0);
1007
+ process2.exit(0);
351
1008
  }
352
1009
  const stdin = await getStdin();
353
1010
  if (stdin.length === 0) {
@@ -358,20 +1015,18 @@ claude-powerline - Beautiful powerline statusline for Claude Code
358
1015
  Usage: claude-powerline [options]
359
1016
 
360
1017
  Options:
361
- --dark Use dark color scheme
362
- --install-fonts Install powerline fonts to system
363
- -h, --help Show this help
1018
+ --theme=THEME Set theme: light, dark, custom
1019
+ --usage=TYPE Usage display: cost, tokens, both, breakdown
1020
+ --session-budget=AMOUNT Set session budget for percentage tracking
1021
+ --daily-budget=AMOUNT Set daily budget for percentage tracking
1022
+ --config=PATH Use custom config file path
1023
+ --install-fonts Install powerline fonts to system
1024
+ --print-default-config Print default configuration template
1025
+ -h, --help Show this help
364
1026
 
365
- Usage in Claude Code settings.json:
366
- {
367
- "statusLine": {
368
- "type": "command",
369
- "command": "claude-powerline",
370
- "padding": 0
371
- }
372
- }
1027
+ Run 'claude-powerline --print-default-config' to see configuration options.
373
1028
  `);
374
- process.exit(1);
1029
+ process2.exit(1);
375
1030
  }
376
1031
  let hookData;
377
1032
  try {
@@ -381,15 +1036,17 @@ Usage in Claude Code settings.json:
381
1036
  "Error: Invalid JSON input:",
382
1037
  error instanceof Error ? error.message : String(error)
383
1038
  );
384
- process.exit(1);
1039
+ process2.exit(1);
385
1040
  }
386
- const renderer = new PowerlineRenderer();
387
- const statusline = await renderer.generateStatusline(hookData, style);
1041
+ const projectDir = hookData.workspace?.project_dir;
1042
+ const config = loadConfigFromCLI(process2.argv, projectDir);
1043
+ const renderer = new PowerlineRenderer(config);
1044
+ const statusline = await renderer.generateStatusline(hookData);
388
1045
  console.log(statusline);
389
1046
  } catch (error) {
390
1047
  const errorMessage = error instanceof Error ? error.message : String(error);
391
1048
  console.error("Error generating statusline:", errorMessage);
392
- process.exit(1);
1049
+ process2.exit(1);
393
1050
  }
394
1051
  }
395
1052
  main();