Skip to content

Commit 566d5fd

Browse files
authored
Merge pull request #234 from shelltime/refactor/cc-statusline-params-struct
refactor(commands): replace positional params with statuslineParams struct
2 parents acc0341 + c06172c commit 566d5fd

2 files changed

Lines changed: 195 additions & 39 deletions

File tree

commands/cc_statusline.go

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,20 @@ func commandCCStatusline(c *cli.Context) error {
8989
}
9090

9191
// Format and output
92-
output := formatStatuslineOutput(data.Model.DisplayName, data.Cost.TotalCostUSD, result.Cost, result.SessionSeconds, contextPercent, result.GitBranch, result.GitDirty, result.FiveHourUtilization, result.SevenDayUtilization, result.UserLogin, result.WebEndpoint, data.SessionID)
92+
output := formatStatuslineOutput(statuslineParams{
93+
ModelName: data.Model.DisplayName,
94+
SessionCost: data.Cost.TotalCostUSD,
95+
DailyCost: result.Cost,
96+
SessionSeconds: result.SessionSeconds,
97+
ContextPercent: contextPercent,
98+
GitBranch: result.GitBranch,
99+
GitDirty: result.GitDirty,
100+
FiveHourUtil: result.FiveHourUtilization,
101+
SevenDayUtil: result.SevenDayUtilization,
102+
UserLogin: result.UserLogin,
103+
WebEndpoint: result.WebEndpoint,
104+
SessionID: data.SessionID,
105+
})
93106
fmt.Println(output)
94107

95108
return nil
@@ -145,13 +158,28 @@ func calculateContextPercent(cw model.CCStatuslineContextWindow) float64 {
145158
return float64(currentTokens) / float64(cw.ContextWindowSize) * 100
146159
}
147160

148-
func formatStatuslineOutput(modelName string, sessionCost, dailyCost float64, sessionSeconds int, contextPercent float64, gitBranch string, gitDirty bool, fiveHourUtil, sevenDayUtil *float64, userLogin, webEndpoint, sessionID string) string {
161+
type statuslineParams struct {
162+
ModelName string
163+
SessionCost float64
164+
DailyCost float64
165+
SessionSeconds int
166+
ContextPercent float64
167+
GitBranch string
168+
GitDirty bool
169+
FiveHourUtil *float64
170+
SevenDayUtil *float64
171+
UserLogin string
172+
WebEndpoint string
173+
SessionID string
174+
}
175+
176+
func formatStatuslineOutput(p statuslineParams) string {
149177
var parts []string
150178

151179
// Git info FIRST (green)
152-
if gitBranch != "" {
153-
gitStr := gitBranch
154-
if gitDirty {
180+
if p.GitBranch != "" {
181+
gitStr := p.GitBranch
182+
if p.GitDirty {
155183
gitStr += "*"
156184
}
157185
parts = append(parts, color.Green.Sprintf("🌿 %s", gitStr))
@@ -160,22 +188,22 @@ func formatStatuslineOutput(modelName string, sessionCost, dailyCost float64, se
160188
}
161189

162190
// Model name
163-
modelStr := fmt.Sprintf("🤖 %s", modelName)
191+
modelStr := fmt.Sprintf("🤖 %s", p.ModelName)
164192
parts = append(parts, modelStr)
165193

166194
// Session cost (cyan) - clickable link to session page when user login and session ID are available
167-
sessionStr := color.Cyan.Sprintf("💰 $%.2f", sessionCost)
168-
if userLogin != "" && webEndpoint != "" && sessionID != "" {
169-
url := fmt.Sprintf("%s/users/%s/coding-agent/session/%s", webEndpoint, userLogin, sessionID)
195+
sessionStr := color.Cyan.Sprintf("💰 $%.2f", p.SessionCost)
196+
if p.UserLogin != "" && p.WebEndpoint != "" && p.SessionID != "" {
197+
url := fmt.Sprintf("%s/users/%s/coding-agent/session/%s", p.WebEndpoint, p.UserLogin, p.SessionID)
170198
sessionStr = wrapOSC8Link(url, sessionStr)
171199
}
172200
parts = append(parts, sessionStr)
173201

174202
// Daily cost (yellow) - clickable link to coding agent page when user login is available
175-
if dailyCost > 0 {
176-
dailyStr := color.Yellow.Sprintf("📊 $%.2f", dailyCost)
177-
if userLogin != "" && webEndpoint != "" {
178-
url := fmt.Sprintf("%s/users/%s/coding-agent/claude-code", webEndpoint, userLogin)
203+
if p.DailyCost > 0 {
204+
dailyStr := color.Yellow.Sprintf("📊 $%.2f", p.DailyCost)
205+
if p.UserLogin != "" && p.WebEndpoint != "" {
206+
url := fmt.Sprintf("%s/users/%s/coding-agent/claude-code", p.WebEndpoint, p.UserLogin)
179207
dailyStr = wrapOSC8Link(url, dailyStr)
180208
}
181209
parts = append(parts, dailyStr)
@@ -185,14 +213,14 @@ func formatStatuslineOutput(modelName string, sessionCost, dailyCost float64, se
185213

186214
// Quota utilization (macOS only - requires Keychain for OAuth token)
187215
if runtime.GOOS == "darwin" {
188-
parts = append(parts, formatQuotaPart(fiveHourUtil, sevenDayUtil))
216+
parts = append(parts, formatQuotaPart(p.FiveHourUtil, p.SevenDayUtil))
189217
}
190218

191219
// AI agent time (magenta) - clickable link to user profile
192-
if sessionSeconds > 0 {
193-
timeStr := color.Magenta.Sprintf("⏱️ %s", formatSessionDuration(sessionSeconds))
194-
if userLogin != "" && webEndpoint != "" {
195-
url := fmt.Sprintf("%s/users/%s", webEndpoint, userLogin)
220+
if p.SessionSeconds > 0 {
221+
timeStr := color.Magenta.Sprintf("⏱️ %s", formatSessionDuration(p.SessionSeconds))
222+
if p.UserLogin != "" && p.WebEndpoint != "" {
223+
url := fmt.Sprintf("%s/users/%s", p.WebEndpoint, p.UserLogin)
196224
timeStr = wrapOSC8Link(url, timeStr)
197225
}
198226
parts = append(parts, timeStr)
@@ -203,12 +231,12 @@ func formatStatuslineOutput(modelName string, sessionCost, dailyCost float64, se
203231
// Context percentage with color coding
204232
var contextStr string
205233
switch {
206-
case contextPercent >= 80:
207-
contextStr = color.Red.Sprintf("📈 %.0f%%", contextPercent)
208-
case contextPercent >= 50:
209-
contextStr = color.Yellow.Sprintf("📈 %.0f%%", contextPercent)
234+
case p.ContextPercent >= 80:
235+
contextStr = color.Red.Sprintf("📈 %.0f%%", p.ContextPercent)
236+
case p.ContextPercent >= 50:
237+
contextStr = color.Yellow.Sprintf("📈 %.0f%%", p.ContextPercent)
210238
default:
211-
contextStr = color.Green.Sprintf("📈 %.0f%%", contextPercent)
239+
contextStr = color.Green.Sprintf("📈 %.0f%%", p.ContextPercent)
212240
}
213241
parts = append(parts, contextStr)
214242

commands/cc_statusline_test.go

Lines changed: 144 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,14 @@ func (s *CCStatuslineTestSuite) TestGetDaemonInfo_UsesDefaultSocketPath() {
151151
// formatStatuslineOutput Tests
152152

153153
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_AllValues() {
154-
output := formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "main", false, nil, nil, "", "", "")
154+
output := formatStatuslineOutput(statuslineParams{
155+
ModelName: "claude-opus-4",
156+
SessionCost: 1.23,
157+
DailyCost: 4.56,
158+
SessionSeconds: 3661,
159+
ContextPercent: 75.0,
160+
GitBranch: "main",
161+
})
155162

156163
// Should contain all components
157164
assert.Contains(s.T(), output, "🌿 main")
@@ -163,46 +170,86 @@ func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_AllValues() {
163170
}
164171

165172
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_WithDirtyBranch() {
166-
output := formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "feature/test", true, nil, nil, "", "", "")
173+
output := formatStatuslineOutput(statuslineParams{
174+
ModelName: "claude-opus-4",
175+
SessionCost: 1.23,
176+
DailyCost: 4.56,
177+
SessionSeconds: 3661,
178+
ContextPercent: 75.0,
179+
GitBranch: "feature/test",
180+
GitDirty: true,
181+
})
167182

168183
// Should contain branch with asterisk for dirty
169184
assert.Contains(s.T(), output, "🌿 feature/test*")
170185
assert.Contains(s.T(), output, "🤖 claude-opus-4")
171186
}
172187

173188
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_NoBranch() {
174-
output := formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "", false, nil, nil, "", "", "")
189+
output := formatStatuslineOutput(statuslineParams{
190+
ModelName: "claude-opus-4",
191+
SessionCost: 1.23,
192+
DailyCost: 4.56,
193+
SessionSeconds: 3661,
194+
ContextPercent: 75.0,
195+
})
175196

176197
// Should show "-" for no branch
177198
assert.Contains(s.T(), output, "🌿 -")
178199
assert.Contains(s.T(), output, "🤖 claude-opus-4")
179200
}
180201

181202
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_ZeroDailyCost() {
182-
output := formatStatuslineOutput("claude-sonnet", 0.50, 0, 300, 50.0, "main", false, nil, nil, "", "", "")
203+
output := formatStatuslineOutput(statuslineParams{
204+
ModelName: "claude-sonnet",
205+
SessionCost: 0.50,
206+
SessionSeconds: 300,
207+
ContextPercent: 50.0,
208+
GitBranch: "main",
209+
})
183210

184211
// Should show "-" for zero daily cost
185212
assert.Contains(s.T(), output, "📊 -")
186213
assert.Contains(s.T(), output, "5m0s") // Session time (300 seconds = 5m)
187214
}
188215

189216
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_ZeroSessionSeconds() {
190-
output := formatStatuslineOutput("claude-sonnet", 0.50, 1.0, 0, 50.0, "main", false, nil, nil, "", "", "")
217+
output := formatStatuslineOutput(statuslineParams{
218+
ModelName: "claude-sonnet",
219+
SessionCost: 0.50,
220+
DailyCost: 1.0,
221+
ContextPercent: 50.0,
222+
GitBranch: "main",
223+
})
191224

192225
// Should show "-" for zero session seconds
193226
assert.Contains(s.T(), output, "⏱️ -")
194227
}
195228

196229
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_HighContextPercentage() {
197-
output := formatStatuslineOutput("test-model", 1.0, 1.0, 60, 85.0, "main", false, nil, nil, "", "", "")
230+
output := formatStatuslineOutput(statuslineParams{
231+
ModelName: "test-model",
232+
SessionCost: 1.0,
233+
DailyCost: 1.0,
234+
SessionSeconds: 60,
235+
ContextPercent: 85.0,
236+
GitBranch: "main",
237+
})
198238

199239
// Should contain the percentage (color codes may vary)
200240
assert.Contains(s.T(), output, "85%")
201241
assert.Contains(s.T(), output, "1m0s")
202242
}
203243

204244
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_LowContextPercentage() {
205-
output := formatStatuslineOutput("test-model", 1.0, 1.0, 45, 25.0, "main", false, nil, nil, "", "", "")
245+
output := formatStatuslineOutput(statuslineParams{
246+
ModelName: "test-model",
247+
SessionCost: 1.0,
248+
DailyCost: 1.0,
249+
SessionSeconds: 45,
250+
ContextPercent: 25.0,
251+
GitBranch: "main",
252+
})
206253

207254
// Should contain the percentage
208255
assert.Contains(s.T(), output, "25%")
@@ -330,7 +377,17 @@ func (s *CCStatuslineTestSuite) TestFormatQuotaPart_ContainsLink() {
330377
}
331378

332379
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_SessionCostWithLink() {
333-
output := formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "main", false, nil, nil, "testuser", "https://shelltime.xyz", "session-abc123")
380+
output := formatStatuslineOutput(statuslineParams{
381+
ModelName: "claude-opus-4",
382+
SessionCost: 1.23,
383+
DailyCost: 4.56,
384+
SessionSeconds: 3661,
385+
ContextPercent: 75.0,
386+
GitBranch: "main",
387+
UserLogin: "testuser",
388+
WebEndpoint: "https://shelltime.xyz",
389+
SessionID: "session-abc123",
390+
})
334391

335392
// Should contain OSC8 link wrapping session cost
336393
assert.Contains(s.T(), output, "shelltime.xyz/users/testuser/coding-agent/session/session-abc123")
@@ -340,23 +397,60 @@ func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_SessionCostWithLink()
340397

341398
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_SessionCostWithoutLink() {
342399
// No userLogin - should not have link
343-
output := formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "main", false, nil, nil, "", "https://shelltime.xyz", "session-abc123")
400+
output := formatStatuslineOutput(statuslineParams{
401+
ModelName: "claude-opus-4",
402+
SessionCost: 1.23,
403+
DailyCost: 4.56,
404+
SessionSeconds: 3661,
405+
ContextPercent: 75.0,
406+
GitBranch: "main",
407+
WebEndpoint: "https://shelltime.xyz",
408+
SessionID: "session-abc123",
409+
})
344410
assert.Contains(s.T(), output, "$1.23")
345411
assert.NotContains(s.T(), output, "coding-agent/session/")
346412

347413
// No sessionID - should not have link
348-
output = formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "main", false, nil, nil, "testuser", "https://shelltime.xyz", "")
414+
output = formatStatuslineOutput(statuslineParams{
415+
ModelName: "claude-opus-4",
416+
SessionCost: 1.23,
417+
DailyCost: 4.56,
418+
SessionSeconds: 3661,
419+
ContextPercent: 75.0,
420+
GitBranch: "main",
421+
UserLogin: "testuser",
422+
WebEndpoint: "https://shelltime.xyz",
423+
})
349424
assert.Contains(s.T(), output, "$1.23")
350425
assert.NotContains(s.T(), output, "coding-agent/session/")
351426

352427
// No webEndpoint - should not have link
353-
output = formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "main", false, nil, nil, "testuser", "", "session-abc123")
428+
output = formatStatuslineOutput(statuslineParams{
429+
ModelName: "claude-opus-4",
430+
SessionCost: 1.23,
431+
DailyCost: 4.56,
432+
SessionSeconds: 3661,
433+
ContextPercent: 75.0,
434+
GitBranch: "main",
435+
UserLogin: "testuser",
436+
SessionID: "session-abc123",
437+
})
354438
assert.Contains(s.T(), output, "$1.23")
355439
assert.NotContains(s.T(), output, "coding-agent/session/")
356440
}
357441

358442
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_TimeWithProfileLink() {
359-
output := formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "main", false, nil, nil, "testuser", "https://shelltime.xyz", "session-abc123")
443+
output := formatStatuslineOutput(statuslineParams{
444+
ModelName: "claude-opus-4",
445+
SessionCost: 1.23,
446+
DailyCost: 4.56,
447+
SessionSeconds: 3661,
448+
ContextPercent: 75.0,
449+
GitBranch: "main",
450+
UserLogin: "testuser",
451+
WebEndpoint: "https://shelltime.xyz",
452+
SessionID: "session-abc123",
453+
})
360454

361455
// Should contain OSC8 link wrapping time section to user profile
362456
assert.Contains(s.T(), output, "shelltime.xyz/users/testuser")
@@ -365,21 +459,48 @@ func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_TimeWithProfileLink()
365459

366460
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_TimeWithoutProfileLink() {
367461
// No userLogin - should not have profile link on time
368-
output := formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "main", false, nil, nil, "", "https://shelltime.xyz", "session-abc123")
462+
output := formatStatuslineOutput(statuslineParams{
463+
ModelName: "claude-opus-4",
464+
SessionCost: 1.23,
465+
DailyCost: 4.56,
466+
SessionSeconds: 3661,
467+
ContextPercent: 75.0,
468+
GitBranch: "main",
469+
WebEndpoint: "https://shelltime.xyz",
470+
SessionID: "session-abc123",
471+
})
369472
assert.Contains(s.T(), output, "1h1m")
370473
// The time section should not contain a link to users/ profile
371474
// Count occurrences of "shelltime.xyz/users/" - should only be in session cost and daily cost links
372475
assert.NotContains(s.T(), output, "shelltime.xyz/users//")
373476

374477
// No webEndpoint - should not have profile link on time
375-
output = formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "main", false, nil, nil, "testuser", "", "session-abc123")
478+
output = formatStatuslineOutput(statuslineParams{
479+
ModelName: "claude-opus-4",
480+
SessionCost: 1.23,
481+
DailyCost: 4.56,
482+
SessionSeconds: 3661,
483+
ContextPercent: 75.0,
484+
GitBranch: "main",
485+
UserLogin: "testuser",
486+
SessionID: "session-abc123",
487+
})
376488
assert.Contains(s.T(), output, "1h1m")
377489
}
378490

379491
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_WithQuota() {
380492
fh := 45.0
381493
sd := 23.0
382-
output := formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "main", false, &fh, &sd, "", "", "")
494+
output := formatStatuslineOutput(statuslineParams{
495+
ModelName: "claude-opus-4",
496+
SessionCost: 1.23,
497+
DailyCost: 4.56,
498+
SessionSeconds: 3661,
499+
ContextPercent: 75.0,
500+
GitBranch: "main",
501+
FiveHourUtil: &fh,
502+
SevenDayUtil: &sd,
503+
})
383504

384505
if runtime.GOOS == "darwin" {
385506
assert.Contains(s.T(), output, "5h:45%")
@@ -391,7 +512,14 @@ func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_WithQuota() {
391512
}
392513

393514
func (s *CCStatuslineTestSuite) TestFormatStatuslineOutput_WithoutQuota() {
394-
output := formatStatuslineOutput("claude-opus-4", 1.23, 4.56, 3661, 75.0, "main", false, nil, nil, "", "", "")
515+
output := formatStatuslineOutput(statuslineParams{
516+
ModelName: "claude-opus-4",
517+
SessionCost: 1.23,
518+
DailyCost: 4.56,
519+
SessionSeconds: 3661,
520+
ContextPercent: 75.0,
521+
GitBranch: "main",
522+
})
395523

396524
if runtime.GOOS == "darwin" {
397525
assert.Contains(s.T(), output, "🚦 -")

0 commit comments

Comments
 (0)