Skip to content

Commit b34f3fb

Browse files
committed
fix review findings and add extension tools [minor-release]
1 parent 046ce0d commit b34f3fb

9 files changed

Lines changed: 91 additions & 76 deletions

File tree

cmd/cheat.go

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,18 @@ var cheatCmd = &cobra.Command{
1717
if len(args) > 0 {
1818
return nil, cobra.ShellCompDirectiveNoFileComp
1919
}
20-
return cheatsheet.AllNames(), cobra.ShellCompDirectiveNoFileComp
20+
suggestions := append([]string{"list"}, cheatsheet.AllNames()...)
21+
return suggestions, cobra.ShellCompDirectiveNoFileComp
2122
},
2223
Run: func(cmd *cobra.Command, args []string) {
23-
if err := cheatsheet.Print(args[0]); err != nil {
24-
utils.PrintFatal(err.Error(), nil)
24+
if args[0] == "list" {
25+
for _, s := range cheatsheet.List() {
26+
utils.PrintInfo(fmt.Sprintf("%s — %s", s.Name, s.Description))
27+
}
28+
return
2529
}
26-
},
27-
}
28-
29-
var cheatListCmd = &cobra.Command{
30-
Use: "list",
31-
Short: "List available cheat sheets",
32-
Args: cobra.NoArgs,
33-
Run: func(cmd *cobra.Command, args []string) {
34-
for _, s := range cheatsheet.List() {
35-
utils.PrintInfo(fmt.Sprintf("%s — %s", s.Name, s.Description))
30+
if err := cheatsheet.Print(args[0]); err != nil {
31+
utils.PrintFatal("failed to print cheat sheet", err)
3632
}
3733
},
3834
}
39-
40-
func init() {
41-
cheatCmd.AddCommand(cheatListCmd)
42-
}

cmd/extend.go

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,34 @@ import (
66
"github.com/tanq16/cli-productivity-suite/internal/runner"
77
)
88

9-
var extendCheckFlag bool
9+
var extendFlags struct {
10+
check bool
11+
}
1012

1113
var extendCmd = &cobra.Command{
1214
Use: "extend <pack-name>",
13-
Short: "Install extension tool packs (e.g., cloud-sec, app-sec)",
15+
Short: "Install extension tool packs (e.g., security, cloudsec, appsec)",
1416
Args: cobra.ExactArgs(1),
1517
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
1618
if len(args) > 0 {
1719
return nil, cobra.ShellCompDirectiveNoFileComp
1820
}
19-
return runner.ExtensionPackNames(), cobra.ShellCompDirectiveNoFileComp
21+
suggestions := append([]string{"list"}, runner.ExtensionPackNames()...)
22+
return suggestions, cobra.ShellCompDirectiveNoFileComp
2023
},
2124
Run: func(cmd *cobra.Command, args []string) {
22-
if extendCheckFlag {
25+
if args[0] == "list" {
26+
runner.ExtendList()
27+
return
28+
}
29+
if extendFlags.check {
2330
runner.ExtendCheck(args[0], ghToken)
2431
} else {
2532
runner.Extend(args[0], ghToken)
2633
}
2734
},
2835
}
2936

30-
var extendListCmd = &cobra.Command{
31-
Use: "list",
32-
Short: "List available extension packs",
33-
Args: cobra.NoArgs,
34-
Run: func(cmd *cobra.Command, args []string) {
35-
runner.ExtendList()
36-
},
37-
}
38-
3937
func init() {
40-
extendCmd.Flags().BoolVar(&extendCheckFlag, "check", false, "Check for updates instead of installing")
41-
extendCmd.AddCommand(extendListCmd)
38+
extendCmd.Flags().BoolVar(&extendFlags.check, "check", false, "Check for updates instead of installing")
4239
}

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ module github.com/tanq16/cli-productivity-suite
33
go 1.25.0
44

55
require (
6-
charm.land/bubbles/v2 v2.0.0
6+
charm.land/bubbles/v2 v2.1.0
77
charm.land/bubbletea/v2 v2.0.2
88
charm.land/lipgloss/v2 v2.0.2
9-
github.com/rs/zerolog v1.34.0
9+
github.com/rs/zerolog v1.35.0
1010
github.com/spf13/cobra v1.10.2
1111
)
1212

@@ -22,9 +22,9 @@ require (
2222
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
2323
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2424
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
25-
github.com/mattn/go-colorable v0.1.13 // indirect
25+
github.com/mattn/go-colorable v0.1.14 // indirect
2626
github.com/mattn/go-isatty v0.0.20 // indirect
27-
github.com/mattn/go-runewidth v0.0.20 // indirect
27+
github.com/mattn/go-runewidth v0.0.21 // indirect
2828
github.com/muesli/cancelreader v0.2.2 // indirect
2929
github.com/rivo/uniseg v0.4.7 // indirect
3030
github.com/spf13/pflag v1.0.9 // indirect

go.sum

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
2-
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
1+
charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g=
2+
charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY=
33
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
44
charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
55
charm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs=
@@ -28,29 +28,23 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE
2828
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
2929
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
3030
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
31-
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
3231
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
33-
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
3432
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
3533
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
3634
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
3735
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
38-
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
39-
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
40-
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
41-
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
36+
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
37+
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
4238
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
4339
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
44-
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
45-
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
40+
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
41+
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
4642
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
4743
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
48-
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
4944
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
5045
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
51-
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
52-
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
53-
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
46+
github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI=
47+
github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
5448
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
5549
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
5650
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
@@ -63,9 +57,7 @@ golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM
6357
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
6458
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
6559
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
66-
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6760
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
68-
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6961
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
7062
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
7163
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/cheatsheet/uv.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import (
77
)
88

99
var (
10-
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("12")).PaddingBottom(1)
11-
headingStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("11"))
12-
cmdStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("10"))
13-
noteStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Italic(true)
14-
dividerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
10+
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.ANSIColor(12)).PaddingBottom(1)
11+
headingStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.ANSIColor(11))
12+
cmdStyle = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(10))
13+
noteStyle = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(8)).Italic(true)
14+
dividerStyle = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(8))
1515
)
1616

1717
func buildUVSheet() string {

internal/github/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type Client struct {
1717

1818
func NewClient(token string) *Client {
1919
return &Client{
20-
httpClient: &http.Client{Timeout: 30 * time.Second},
20+
httpClient: &http.Client{Timeout: 60 * time.Second},
2121
token: token,
2222
}
2323
}

internal/runner/runner.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package runner
22

33
import (
4+
"context"
45
"fmt"
56
"os"
67
"os/exec"
@@ -47,15 +48,17 @@ func Init(ghToken string) {
4748
}
4849
utils.PrintIndentedSuccess("prerequisites OK")
4950

50-
var sudoDone chan struct{}
51+
var sudoCancel context.CancelFunc
5152
if PhaseNeedsSudo(p, registry.SystemPackage, registry.CloudCLI, registry.LanguageRuntime) {
5253
cached := exec.Command("sudo", "-n", "-v").Run() == nil
5354
utils.ClearLines(2)
5455
utils.PrintRunning("(Running) Phase 1: Authenticating sudo")
5556
if err := EnsureSudo(); err != nil {
5657
utils.PrintFatal("sudo authentication failed", err)
5758
}
58-
sudoDone = StartSudoRefresh()
59+
ctx, cancel := context.WithCancel(context.Background())
60+
sudoCancel = cancel
61+
StartSudoRefresh(ctx)
5962
if cached {
6063
utils.ClearLines(1)
6164
} else {
@@ -126,8 +129,8 @@ func Init(ghToken string) {
126129

127130
runPostInstall(p)
128131

129-
if sudoDone != nil {
130-
close(sudoDone)
132+
if sudoCancel != nil {
133+
sudoCancel()
131134
}
132135

133136
st.LastInit = time.Now()
@@ -283,7 +286,7 @@ func Install(args []string, ghToken string) {
283286
}
284287
}
285288

286-
var sudoDone chan struct{}
289+
var sudoCancel context.CancelFunc
287290
needsSudo := false
288291
for _, t := range tools {
289292
if ToolNeedsSudo(t, p) {
@@ -297,7 +300,9 @@ func Install(args []string, ghToken string) {
297300
if err := EnsureSudo(); err != nil {
298301
utils.PrintFatal("sudo authentication failed", err)
299302
}
300-
sudoDone = StartSudoRefresh()
303+
ctx, cancel := context.WithCancel(context.Background())
304+
sudoCancel = cancel
305+
StartSudoRefresh(ctx)
301306
if cached {
302307
utils.ClearLines(1)
303308
} else {
@@ -326,8 +331,8 @@ func Install(args []string, ghToken string) {
326331

327332
installPostTasks(tools, p)
328333

329-
if sudoDone != nil {
330-
close(sudoDone)
334+
if sudoCancel != nil {
335+
sudoCancel()
331336
}
332337

333338
if err := st.Save(); err != nil {
@@ -428,7 +433,8 @@ func SelfUpdate(appVersion string) {
428433
if err := EnsureSudo(); err != nil {
429434
utils.PrintFatal("sudo authentication failed", err)
430435
}
431-
sudoDone := StartSudoRefresh()
436+
sudoCtx, sudoCancel := context.WithCancel(context.Background())
437+
StartSudoRefresh(sudoCtx)
432438
if cached {
433439
utils.ClearLines(1)
434440
} else {
@@ -455,7 +461,12 @@ func SelfUpdate(appVersion string) {
455461
destPath = "/usr/local/bin/cps"
456462
}
457463
rmCmd := exec.Command("sudo", "rm", "-f", destPath)
464+
var rmStderr strings.Builder
465+
rmCmd.Stderr = &rmStderr
458466
if err := rmCmd.Run(); err != nil {
467+
if detail := strings.TrimSpace(rmStderr.String()); detail != "" {
468+
err = fmt.Errorf("%s: %w", detail, err)
469+
}
459470
utils.PrintFatal(fmt.Sprintf("failed to remove old binary at %s", destPath), err)
460471
}
461472
cpCmd := exec.Command("sudo", "cp", tmpBinary, destPath)
@@ -470,7 +481,7 @@ func SelfUpdate(appVersion string) {
470481
}
471482
utils.ClearLines(1)
472483

473-
close(sudoDone)
484+
sudoCancel()
474485
utils.PrintSuccess(fmt.Sprintf("updated cps: %s → %s", appVersion, release.TagName))
475486
}
476487

internal/runner/sudo.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package runner
22

33
import (
4+
"context"
45
"fmt"
56
"os"
67
"os/exec"
@@ -21,21 +22,19 @@ func EnsureSudo() error {
2122
return nil
2223
}
2324

24-
func StartSudoRefresh() chan struct{} {
25-
done := make(chan struct{})
25+
func StartSudoRefresh(ctx context.Context) {
2626
go func() {
2727
ticker := time.NewTicker(60 * time.Second)
2828
defer ticker.Stop()
2929
for {
3030
select {
31-
case <-done:
31+
case <-ctx.Done():
3232
return
3333
case <-ticker.C:
3434
exec.Command("sudo", "-n", "-v").Run()
3535
}
3636
}
3737
}()
38-
return done
3938
}
4039

4140
func PhaseNeedsSudo(p platform.Platform, kinds ...registry.ToolKind) bool {

utils/printer.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ package utils
33
import (
44
"fmt"
55
"os"
6+
"strings"
67

78
"charm.land/lipgloss/v2"
89
"github.com/rs/zerolog/log"
910
)
1011

1112
var (
12-
ColorBlue = lipgloss.Color("12") // Bright Blue
13-
ColorGreen = lipgloss.Color("10") // Bright Green
14-
ColorRed = lipgloss.Color("9") // Bright Red
15-
ColorYellow = lipgloss.Color("11") // Bright Yellow
13+
ColorBlue = lipgloss.ANSIColor(12) // Bright Blue
14+
ColorGreen = lipgloss.ANSIColor(10) // Bright Green
15+
ColorRed = lipgloss.ANSIColor(9) // Bright Red
16+
ColorYellow = lipgloss.ANSIColor(11) // Bright Yellow
1617

1718
infoStyle = lipgloss.NewStyle().Foreground(ColorBlue)
1819
successStyle = lipgloss.NewStyle().Foreground(ColorGreen)
@@ -160,3 +161,26 @@ func ClearPreviousLine() {
160161
}
161162
fmt.Print("\033[A\033[2K")
162163
}
164+
165+
func PrintProgress(label string, percent int) {
166+
if percent > 100 {
167+
percent = 100
168+
}
169+
170+
if GlobalDebugFlag {
171+
log.Info().Str("package", "utils").Int("percent", percent).Msg(label)
172+
return
173+
}
174+
175+
if GlobalForAIFlag {
176+
fmt.Printf("[PROGRESS] %s: %d%%\n", label, percent)
177+
return
178+
}
179+
180+
const barWidth = 10
181+
filled := barWidth * percent / 100
182+
empty := barWidth - filled
183+
184+
bar := strings.Repeat("⣿", filled) + strings.Repeat("⣀", empty)
185+
fmt.Println(infoStyle.Render(fmt.Sprintf(" ↻ %s: %s %d%%", label, bar, percent)))
186+
}

0 commit comments

Comments
 (0)