Skip to content

Commit 782b62a

Browse files
gluonfieldclaude
andcommitted
feat: show all workspaces and config in whoami
Display all workspaces with role and per-workspace billing. Show active workspace and project from resolved config. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9f9c8e3 commit 782b62a

1 file changed

Lines changed: 77 additions & 27 deletions

File tree

‎cmd/whoami.go‎

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package cmd
22

33
import (
44
"fmt"
5-
"strconv"
65
"strings"
76
"sync"
87
"time"
@@ -15,14 +14,6 @@ func formatCents(cents int) string {
1514
return fmt.Sprintf("$%.2f", float64(cents)/100)
1615
}
1716

18-
func formatSubtotal(s string) string {
19-
v, err := strconv.ParseFloat(s, 64)
20-
if err != nil {
21-
return "$" + s
22-
}
23-
return fmt.Sprintf("$%.2f", v)
24-
}
25-
2617
func shortDate(raw string) string {
2718
t, err := time.Parse(time.RFC3339Nano, raw)
2819
if err != nil {
@@ -41,11 +32,11 @@ ink whoami --json`,
4132
client := newClient()
4233

4334
var (
44-
result *gql.AccountStatusResponse
45-
accErr error
46-
usage *gql.UsageBillBreakdownResponse
47-
usageErr error
48-
wg sync.WaitGroup
35+
result *gql.AccountStatusResponse
36+
accErr error
37+
wsList *gql.ListWorkspacesResponse
38+
wsErr error
39+
wg sync.WaitGroup
4940
)
5041

5142
wg.Add(2)
@@ -55,7 +46,7 @@ ink whoami --json`,
5546
}()
5647
go func() {
5748
defer wg.Done()
58-
usage, usageErr = gql.UsageBillBreakdown(ctx(), client, wsPtr())
49+
wsList, wsErr = gql.ListWorkspaces(ctx(), client)
5950
}()
6051
wg.Wait()
6152

@@ -68,10 +59,44 @@ ink whoami --json`,
6859
fatal("Could not fetch account")
6960
}
7061

62+
// Fetch billing for each workspace in parallel
63+
type wsBilling struct {
64+
slug string
65+
data *gql.UsageBillBreakdownResponse
66+
}
67+
var billings []wsBilling
68+
if wsErr == nil && len(wsList.WorkspaceList) > 0 {
69+
billings = make([]wsBilling, len(wsList.WorkspaceList))
70+
var bwg sync.WaitGroup
71+
for i, ws := range wsList.WorkspaceList {
72+
billings[i].slug = ws.Slug
73+
bwg.Add(1)
74+
go func(idx int, slug string) {
75+
defer bwg.Done()
76+
s := slug
77+
billings[idx].data, _ = gql.UsageBillBreakdown(ctx(), client, &s)
78+
}(i, ws.Slug)
79+
}
80+
bwg.Wait()
81+
}
82+
7183
if jsonOutput {
7284
out := map[string]any{"account": a}
73-
if usageErr == nil {
74-
out["usage"] = usage.UsageBillBreakdown
85+
if wsErr == nil {
86+
out["workspaces"] = wsList.WorkspaceList
87+
}
88+
if len(billings) > 0 {
89+
bmap := make(map[string]any)
90+
for _, b := range billings {
91+
if b.data != nil {
92+
bmap[b.slug] = b.data.UsageBillBreakdown
93+
}
94+
}
95+
out["billing"] = bmap
96+
}
97+
out["config"] = map[string]string{
98+
"workspace": cfg.Workspace,
99+
"project": cfg.Project,
75100
}
76101
printJSON(out)
77102
return
@@ -81,24 +106,49 @@ ink whoami --json`,
81106
if a.Email != nil {
82107
d.kv("Email", *a.Email)
83108
}
84-
d.kv("Workspace", a.DefaultWorkspace)
85109

86110
tier := "free"
87111
if a.SubscriptionTier != nil {
88112
tier = *a.SubscriptionTier
89113
}
90114
d.kv("Plan", tier)
91115

92-
// Usage / Billing
93-
if usageErr == nil {
94-
u := usage.UsageBillBreakdown
95-
period := shortDate(u.PeriodStart) + " – " + shortDate(u.PeriodEnd)
116+
// Config
117+
d.blank()
118+
d.section("Config")
119+
cfgWs := cfg.Workspace
120+
if cfgWs == "" {
121+
cfgWs = dim.Render("(default)")
122+
}
123+
d.kv("Workspace", cfgWs)
124+
cfgProj := cfg.Project
125+
if cfgProj == "" {
126+
cfgProj = dim.Render("(default)")
127+
}
128+
d.kv("Project", cfgProj)
129+
130+
// Workspaces
131+
if wsErr == nil && len(wsList.WorkspaceList) > 0 {
96132
d.blank()
97-
d.section("Usage (" + period + ")")
98-
d.kv("Current Usage", formatSubtotal(u.Subtotal))
99-
d.kv("Current Bill", formatCents(u.CurrentBillCents))
100-
if u.IncludedUsageCents > 0 {
101-
d.kv("Included", formatCents(u.IncludedUsageCents))
133+
d.section("Workspaces")
134+
135+
// Build billing lookup
136+
billingMap := make(map[string]*gql.UsageBillBreakdownResponse)
137+
for _, b := range billings {
138+
if b.data != nil {
139+
billingMap[b.slug] = b.data
140+
}
141+
}
142+
143+
for _, ws := range wsList.WorkspaceList {
144+
role := dim.Render(ws.Role)
145+
line := role
146+
if b, ok := billingMap[ws.Slug]; ok {
147+
u := b.UsageBillBreakdown
148+
period := shortDate(u.PeriodStart) + " – " + shortDate(u.PeriodEnd)
149+
line += " " + formatCents(u.CurrentBillCents) + " " + dim.Render(period)
150+
}
151+
d.kv(ws.Slug, line)
102152
}
103153
}
104154

0 commit comments

Comments
 (0)