Skip to content

Commit f14835b

Browse files
committed
feat: admin tool for org membership, typed token claims
- Add internal/workos/admin/ wrapping WorkOS Go SDK for management API - Add cmd/admin/ with join-org and leave-org commands (Cobra) - Add ParseToken/TokenClaims to auth, replacing scattered token parsing - Add GetUserID to auth.Auth interface - Add admin:join-org and admin:leave-org Taskfile tasks - Add admin binary to .gitignore
1 parent 583d1f3 commit f14835b

25 files changed

Lines changed: 585 additions & 135 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ dist/
4242
docs/VISION.md
4343
.hermit/
4444
tmp/
45+
admin

‎Taskfile.yml‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,20 @@ tasks:
159159
cmds:
160160
- doppler run -- go run ./cmd/tero auth status
161161

162+
# ===========================================================================
163+
# Admin (internal tooling, not shipped)
164+
# ===========================================================================
165+
166+
admin:join-org:
167+
desc: Join a client organization (for support/debugging)
168+
cmds:
169+
- doppler run -- go run ./cmd/admin join-org {{.CLI_ARGS}}
170+
171+
admin:leave-org:
172+
desc: Leave a client organization
173+
cmds:
174+
- doppler run -- go run ./cmd/admin leave-org {{.CLI_ARGS}}
175+
162176
# ===========================================================================
163177
# Dependencies
164178
# ===========================================================================

‎cmd/admin/main.go‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Admin tool for internal operations. Not shipped — used via Taskfile only.
2+
//
3+
// Requires WORKOS_API_KEY in the environment (provided by doppler run).
4+
package main
5+
6+
import (
7+
"fmt"
8+
"log/slog"
9+
"os"
10+
11+
"github.com/spf13/cobra"
12+
"github.com/usetero/cli/internal/auth"
13+
"github.com/usetero/cli/internal/config"
14+
"github.com/usetero/cli/internal/keyring"
15+
"github.com/usetero/cli/internal/log"
16+
"github.com/usetero/cli/internal/workos"
17+
workosadmin "github.com/usetero/cli/internal/workos/admin"
18+
)
19+
20+
func main() {
21+
root := &cobra.Command{
22+
Use: "admin",
23+
Short: "Internal admin tooling (not shipped)",
24+
}
25+
26+
root.AddCommand(newJoinOrgCmd())
27+
root.AddCommand(newLeaveOrgCmd())
28+
29+
if err := root.Execute(); err != nil {
30+
os.Exit(1)
31+
}
32+
}
33+
34+
func newJoinOrgCmd() *cobra.Command {
35+
return &cobra.Command{
36+
Use: "join-org <workos-org-id>",
37+
Short: "Join a client organization (for support/debugging)",
38+
Args: cobra.ExactArgs(1),
39+
RunE: func(cmd *cobra.Command, args []string) error {
40+
ctx := cmd.Context()
41+
client, userID, err := setup(cmd)
42+
if err != nil {
43+
return err
44+
}
45+
46+
m, err := client.CreateMembership(ctx, userID, args[0])
47+
if err != nil {
48+
return fmt.Errorf("failed to join org: %w", err)
49+
}
50+
51+
fmt.Printf("Joined org %s (membership %s)\n", m.OrganizationID, m.ID)
52+
fmt.Printf("Run 'tero auth switch' to switch to the organization\n")
53+
return nil
54+
},
55+
}
56+
}
57+
58+
func newLeaveOrgCmd() *cobra.Command {
59+
return &cobra.Command{
60+
Use: "leave-org <workos-org-id>",
61+
Short: "Leave a client organization",
62+
Args: cobra.ExactArgs(1),
63+
RunE: func(cmd *cobra.Command, args []string) error {
64+
ctx := cmd.Context()
65+
client, userID, err := setup(cmd)
66+
if err != nil {
67+
return err
68+
}
69+
70+
orgID := args[0]
71+
m, err := client.FindMembership(ctx, userID, orgID)
72+
if err != nil {
73+
return fmt.Errorf("failed to find membership: %w", err)
74+
}
75+
76+
if err := client.DeleteMembership(ctx, m.ID); err != nil {
77+
return fmt.Errorf("failed to leave org: %w", err)
78+
}
79+
80+
fmt.Printf("Left org %s (deleted membership %s)\n", orgID, m.ID)
81+
return nil
82+
},
83+
}
84+
}
85+
86+
// setup creates the admin client and resolves the current user's WorkOS ID.
87+
func setup(cmd *cobra.Command) (*workosadmin.Client, string, error) {
88+
apiKey := os.Getenv("WORKOS_API_KEY")
89+
if apiKey == "" {
90+
return nil, "", fmt.Errorf("WORKOS_API_KEY is required (use doppler run)")
91+
}
92+
93+
logger := log.Wrap(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
94+
Level: slog.LevelInfo,
95+
})))
96+
scope := log.RootScope(logger)
97+
98+
cliConfig := config.LoadCLIConfig()
99+
tokenStore := keyring.New(cliConfig.Namespace())
100+
workosClient := workos.NewClient(cliConfig.WorkOSClientID, cliConfig.APIEndpoint, cliConfig.PowerSyncEndpoint)
101+
authService := auth.NewService(workosClient, tokenStore, scope)
102+
103+
userID, err := authService.GetUserID(cmd.Context())
104+
if err != nil {
105+
return nil, "", fmt.Errorf("failed to get user ID (are you logged in?): %w", err)
106+
}
107+
108+
return workosadmin.NewClient(apiKey), userID, nil
109+
}

‎cmd/tero/main.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
)
66

77
// version is set via ldflags during build
8-
var version = "dev"
8+
var version = "0.0.1" // TODO: revert to "dev"
99

1010
func main() {
1111
cmd.Execute(version)

‎go.mod‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@ require (
99
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251205162909-7869489d8971
1010
github.com/Khan/genqlient v0.8.1
1111
github.com/atotto/clipboard v0.1.4
12+
github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560
1213
github.com/charmbracelet/x/ansi v0.11.4
1314
github.com/google/uuid v1.6.0
1415
github.com/hashicorp/go-retryablehttp v0.7.8
15-
github.com/lib/pq v1.11.1
1616
github.com/lucasb-eyer/go-colorful v1.3.0
1717
github.com/mattn/go-sqlite3 v1.14.33
1818
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
1919
github.com/rivo/uniseg v0.4.7
20+
github.com/sahilm/fuzzy v0.1.1
2021
github.com/spf13/cobra v1.10.2
2122
github.com/zalando/go-keyring v0.2.6
23+
golang.org/x/mod v0.28.0
2224
gopkg.in/yaml.v3 v3.0.1
2325
)
2426

@@ -32,7 +34,6 @@ require (
3234
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
3335
github.com/charmbracelet/colorprofile v0.4.1 // indirect
3436
github.com/charmbracelet/harmonica v0.2.0 // indirect
35-
github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560 // indirect
3637
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
3738
github.com/charmbracelet/x/term v0.2.2 // indirect
3839
github.com/charmbracelet/x/termios v0.1.1 // indirect
@@ -43,22 +44,20 @@ require (
4344
github.com/danieljoos/wincred v1.2.2 // indirect
4445
github.com/dlclark/regexp2 v1.11.0 // indirect
4546
github.com/godbus/dbus/v5 v5.1.0 // indirect
47+
github.com/google/go-querystring v1.0.0 // indirect
4648
github.com/gorilla/css v1.0.1 // indirect
4749
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
4850
github.com/inconshreveable/mousetrap v1.1.0 // indirect
49-
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
5051
github.com/kr/pretty v0.3.1 // indirect
5152
github.com/mattn/go-runewidth v0.0.19 // indirect
5253
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
5354
github.com/muesli/cancelreader v0.2.2 // indirect
54-
github.com/sahilm/fuzzy v0.1.1 // indirect
5555
github.com/spf13/pflag v1.0.10 // indirect
5656
github.com/vektah/gqlparser/v2 v2.5.31 // indirect
57+
github.com/workos/workos-go/v4 v4.46.1 // indirect
5758
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
5859
github.com/yuin/goldmark v1.7.8 // indirect
5960
github.com/yuin/goldmark-emoji v1.0.5 // indirect
60-
github.com/zeebo/xxh3 v1.1.0 // indirect
61-
golang.org/x/mod v0.28.0 // indirect
6261
golang.org/x/net v0.44.0 // indirect
6362
golang.org/x/sync v0.19.0 // indirect
6463
golang.org/x/sys v0.40.0 // indirect

‎go.sum‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG
4444
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
4545
github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560 h1:j3PW2hypGoPKBy3ooKzW0TFxaxhyHK3NbkLLn4KeRFc=
4646
github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560/go.mod h1:VWATWLRwYP06VYCEur7FsNR2B1xAo7Y+xl1PTbd1ePc=
47-
github.com/charmbracelet/ultraviolet v0.0.0-20260203100311-6c17f5675de7 h1:hmoESCN3V20fp9XToFTQm5hmfOBw9LApEStTbrRGWq0=
48-
github.com/charmbracelet/ultraviolet v0.0.0-20260203100311-6c17f5675de7/go.mod h1:2I+V4H3xExk9BZ3W2UnU/RAkJPYzBOkY7RzEe7tH1vo=
4947
github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI=
5048
github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4=
5149
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
@@ -68,6 +66,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
6866
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
6967
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
7068
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
69+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7170
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7271
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7372
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
@@ -80,6 +79,8 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
8079
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
8180
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
8281
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
82+
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
83+
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
8384
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
8485
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
8586
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -98,16 +99,12 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
9899
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
99100
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
100101
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
101-
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
102-
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
103102
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
104103
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
105104
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
106105
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
107106
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
108107
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
109-
github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI=
110-
github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
111108
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
112109
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
113110
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -141,13 +138,22 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT
141138
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
142139
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
143140
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
141+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
142+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
143+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
144144
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
145145
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
146146
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
147+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
148+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
149+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
150+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
147151
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
148152
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
149153
github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k=
150154
github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
155+
github.com/workos/workos-go/v4 v4.46.1 h1:Gk4EWxLIHxZ8aNlGpvddYyqhtq96fzcTHNqSoq0jVnE=
156+
github.com/workos/workos-go/v4 v4.46.1/go.mod h1:5NYFvNKzTkxTj2n3AbUc/8SOXvpzmP8LsiB31Vw5RCM=
151157
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
152158
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
153159
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
@@ -157,22 +163,16 @@ github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC
157163
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
158164
github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
159165
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
160-
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
161-
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
162166
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
163167
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
164168
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
165169
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
166170
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
167171
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
168172
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
169-
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
170-
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
171173
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
172174
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
173175
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
174-
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
175-
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
176176
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
177177
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
178178
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
@@ -184,5 +184,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
184184
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
185185
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
186186
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
187+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
187188
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
188189
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

‎internal/app/app.go‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/usetero/cli/internal/styles"
3535
"github.com/usetero/cli/internal/tea/cursor"
3636
"github.com/usetero/cli/internal/tea/keymap"
37+
"github.com/usetero/cli/internal/update"
3738
"github.com/usetero/cli/internal/upload"
3839
)
3940

@@ -58,9 +59,10 @@ const (
5859

5960
// Model is the root application model.
6061
type Model struct {
61-
ctx context.Context
62-
theme styles.Theme
63-
scope log.Scope
62+
ctx context.Context
63+
theme styles.Theme
64+
scope log.Scope
65+
version string
6466

6567
// Dependencies
6668
cfg *config.CLIConfig
@@ -101,6 +103,7 @@ func New(
101103
ctx context.Context,
102104
cfg *config.CLIConfig,
103105
theme styles.Theme,
106+
version string,
104107
services api.APIServices,
105108
authService auth.Auth,
106109
prefs preferences.Preferences,
@@ -133,6 +136,7 @@ func New(
133136
ctx: ctx,
134137
theme: theme,
135138
scope: scope,
139+
version: version,
136140
cfg: cfg,
137141
storage: storage,
138142
authService: authService,
@@ -151,9 +155,32 @@ func (m *Model) Init() tea.Cmd {
151155
return tea.Batch(
152156
m.statusBar.Init(),
153157
m.onboarding.Init(),
158+
m.checkForUpdate(),
154159
)
155160
}
156161

162+
// checkForUpdate returns a command that checks GitHub for a newer release.
163+
// Skips the check for dev builds. Errors are logged, never shown to the user.
164+
func (m *Model) checkForUpdate() tea.Cmd {
165+
if m.version == "" || m.version == "dev" {
166+
return nil
167+
}
168+
version := m.version
169+
ctx := m.ctx
170+
scope := m.scope
171+
return func() tea.Msg {
172+
result, err := update.Check(ctx, version, "")
173+
if err != nil {
174+
scope.Debug("update check failed", "error", err)
175+
return nil
176+
}
177+
if result == nil {
178+
return nil
179+
}
180+
return appmsg.Info{Message: fmt.Sprintf("Tero CLI update available: %s → %s", result.Current, result.Latest)}
181+
}
182+
}
183+
157184
// Update handles messages.
158185
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
159186
switch msg.(type) {

‎internal/app/chat/messagelist/render.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func (m *Model) divider(r *round.Model) string {
194194
var prefixStyle lipgloss.Style
195195
if r.State() == round.StateCancelled {
196196
prefix = fmt.Sprintf("◇ Cancelled %s ", durationStr)
197-
prefixStyle = lipgloss.NewStyle().Foreground(colors.ErrorFg).Background(colors.Bg)
197+
prefixStyle = lipgloss.NewStyle().Foreground(colors.ErrorBg).Background(colors.Bg)
198198
} else {
199199
prefix = fmt.Sprintf("◇ Tero %s ", durationStr)
200200
prefixStyle = lipgloss.NewStyle().Foreground(colors.TextMuted).Background(colors.Bg)

‎internal/app/chat/messagelist/round/turn/assistant/blocks/tools/tool.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,9 @@ func (m *Model) renderIcon() string {
246246

247247
switch m.status {
248248
case StatusSuccess:
249-
return lipgloss.NewStyle().Foreground(colors.SuccessFg).Background(bg).Render(IconSuccess)
249+
return lipgloss.NewStyle().Foreground(colors.SuccessBg).Background(bg).Render(IconSuccess)
250250
case StatusError:
251-
return lipgloss.NewStyle().Foreground(colors.ErrorFg).Background(bg).Render(IconError)
251+
return lipgloss.NewStyle().Foreground(colors.ErrorBg).Background(bg).Render(IconError)
252252
case StatusCancelled:
253253
return lipgloss.NewStyle().Foreground(colors.TextMuted).Background(bg).Render(IconCancelled)
254254
default:

0 commit comments

Comments
 (0)