Skip to content

Commit e22c24e

Browse files
committed
feat: replace survey with huh for Escape key support
Switch from AlecAivazis/survey to charmbracelet/huh for all interactive prompts. Pressing Escape now cleanly cancels any prompt instead of requiring Ctrl-C. none
1 parent 0359652 commit e22c24e

5 files changed

Lines changed: 100 additions & 110 deletions

File tree

cmd/deploy.go

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88
"strings"
99
"time"
1010

11-
"github.com/AlecAivazis/survey/v2"
1211
"github.com/briandowns/spinner"
12+
"github.com/charmbracelet/huh"
1313
"github.com/charmbracelet/lipgloss"
1414
"github.com/hostodo/hostodo-cli/pkg/api"
1515
"github.com/hostodo/hostodo-cli/pkg/auth"
@@ -220,12 +220,13 @@ func selectTemplate(templates []api.Template, flag string, jsonMode bool) (*api.
220220
osOptions[i] = t.Name
221221
}
222222
var selected string
223-
prompt := &survey.Select{
224-
Message: "Choose an OS:",
225-
Options: osOptions,
226-
PageSize: 15,
227-
}
228-
if err := survey.AskOne(prompt, &selected); err != nil {
223+
err := huh.NewSelect[string]().
224+
Title("Choose an OS:").
225+
Options(huh.NewOptions(osOptions...)...).
226+
Value(&selected).
227+
Height(15).
228+
Run()
229+
if err != nil {
229230
return nil, err
230231
}
231232
tmpl, _ := findTemplate(templates, selected)
@@ -255,12 +256,13 @@ func selectRegion(regions []api.Region, flag string, jsonMode bool) (*api.Region
255256
regionOptions[i] = r.Name
256257
}
257258
var selected string
258-
prompt := &survey.Select{
259-
Message: "Choose a region:",
260-
Options: regionOptions,
261-
PageSize: 15,
262-
}
263-
if err := survey.AskOne(prompt, &selected); err != nil {
259+
err := huh.NewSelect[string]().
260+
Title("Choose a region:").
261+
Options(huh.NewOptions(regionOptions...)...).
262+
Value(&selected).
263+
Height(15).
264+
Run()
265+
if err != nil {
264266
return nil, err
265267
}
266268
region, _ := findRegion(regions, selected)
@@ -293,12 +295,13 @@ func selectPlan(plans []api.Plan, flag string, jsonMode bool, billingCycle strin
293295
p.Name, price, suffix, p.VCPU, formatRAM(p.RAM), p.Disk, formatBW(p.Bandwidth))
294296
}
295297
var selected string
296-
prompt := &survey.Select{
297-
Message: "Choose a plan:",
298-
Options: planOptions,
299-
PageSize: 15,
300-
}
301-
if err := survey.AskOne(prompt, &selected); err != nil {
298+
err := huh.NewSelect[string]().
299+
Title("Choose a plan:").
300+
Options(huh.NewOptions(planOptions...)...).
301+
Value(&selected).
302+
Height(15).
303+
Run()
304+
if err != nil {
302305
return nil, err
303306
}
304307
planName := strings.Fields(selected)[0]
@@ -347,12 +350,13 @@ func selectSSHKey(client *api.Client, flag string, jsonMode bool) (string, error
347350
keyOptions[i] = fmt.Sprintf("%s (%s)", key.Name, fingerprint)
348351
}
349352
var selected string
350-
prompt := &survey.Select{
351-
Message: "Choose an SSH key:",
352-
Options: keyOptions,
353-
PageSize: 10,
354-
}
355-
if err := survey.AskOne(prompt, &selected); err != nil {
353+
err = huh.NewSelect[string]().
354+
Title("Choose an SSH key:").
355+
Options(huh.NewOptions(keyOptions...)...).
356+
Value(&selected).
357+
Height(10).
358+
Run()
359+
if err != nil {
356360
return "", err
357361
}
358362
return strings.Split(selected, " (")[0], nil
@@ -387,12 +391,12 @@ func confirmDeploy(tmpl *api.Template, region *api.Region, plan *api.Plan, hostn
387391

388392
confirmMsg := fmt.Sprintf("Deploy? (charges $%s to %s ****%s)",
389393
quote.AmountDue, pm.CardType, pm.LastFour)
390-
var confirmed bool
391-
prompt := &survey.Confirm{
392-
Message: confirmMsg,
393-
Default: true,
394-
}
395-
if err := survey.AskOne(prompt, &confirmed); err != nil {
394+
confirmed := true
395+
err := huh.NewConfirm().
396+
Title(confirmMsg).
397+
Value(&confirmed).
398+
Run()
399+
if err != nil {
396400
return false, err
397401
}
398402
return confirmed, nil
@@ -517,12 +521,12 @@ SSH: ssh %s@%s`,
517521
}
518522

519523
func promptSSHConnect(hostname string) {
520-
var connectNow bool
521-
prompt := &survey.Confirm{
522-
Message: "Connect now?",
523-
Default: true,
524-
}
525-
if err := survey.AskOne(prompt, &connectNow); err != nil {
524+
connectNow := true
525+
err := huh.NewConfirm().
526+
Title("Connect now?").
527+
Value(&connectNow).
528+
Run()
529+
if err != nil {
526530
return
527531
}
528532
if connectNow {
@@ -822,12 +826,13 @@ func selectBillingCycle(plans []api.Plan, flag string, jsonMode bool) (string, e
822826
options[i] = billingCycleLabel(c)
823827
}
824828
var selected string
825-
prompt := &survey.Select{
826-
Message: "Choose a billing cycle:",
827-
Options: options,
828-
PageSize: 10,
829-
}
830-
if err := survey.AskOne(prompt, &selected); err != nil {
829+
err := huh.NewSelect[string]().
830+
Title("Choose a billing cycle:").
831+
Options(huh.NewOptions(options...)...).
832+
Value(&selected).
833+
Height(10).
834+
Run()
835+
if err != nil {
831836
return "", err
832837
}
833838
for _, c := range availableCycles {

cmd/keys.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"strings"
77
"time"
88

9-
"github.com/AlecAivazis/survey/v2"
9+
"github.com/charmbracelet/huh"
1010
"github.com/hostodo/hostodo-cli/pkg/api"
1111
"github.com/hostodo/hostodo-cli/pkg/auth"
1212
"github.com/hostodo/hostodo-cli/pkg/config"
@@ -235,11 +235,12 @@ func runKeysRemove(cmd *cobra.Command, args []string) error {
235235
}
236236

237237
var selectedOption string
238-
prompt := &survey.Select{
239-
Message: "Multiple keys found. Select which to delete:",
240-
Options: options,
241-
}
242-
if err := survey.AskOne(prompt, &selectedOption); err != nil {
238+
err = huh.NewSelect[string]().
239+
Title("Multiple keys found. Select which to delete:").
240+
Options(huh.NewOptions(options...)...).
241+
Value(&selectedOption).
242+
Run()
243+
if err != nil {
243244
return err
244245
}
245246

@@ -265,11 +266,11 @@ func runKeysRemove(cmd *cobra.Command, args []string) error {
265266
fmt.Printf("Fingerprint: %s\n", fingerprint)
266267

267268
var confirmed bool
268-
prompt := &survey.Confirm{
269-
Message: "Are you sure?",
270-
Default: false,
271-
}
272-
if err := survey.AskOne(prompt, &confirmed); err != nil {
269+
err = huh.NewConfirm().
270+
Title("Are you sure?").
271+
Value(&confirmed).
272+
Run()
273+
if err != nil {
273274
return err
274275
}
275276

cmd/pay.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package cmd
33
import (
44
"fmt"
55

6-
"github.com/AlecAivazis/survey/v2"
6+
"github.com/charmbracelet/huh"
77
"github.com/hostodo/hostodo-cli/pkg/api"
88
"github.com/hostodo/hostodo-cli/pkg/auth"
99
"github.com/hostodo/hostodo-cli/pkg/config"
@@ -53,11 +53,11 @@ func runPay(cmd *cobra.Command, args []string) error {
5353
if !payYesFlag {
5454
confirmMsg := fmt.Sprintf("Pay invoice %s using your default payment method?", invoiceNumber)
5555
var confirmed bool
56-
prompt := &survey.Confirm{
57-
Message: confirmMsg,
58-
Default: false,
59-
}
60-
if err := survey.AskOne(prompt, &confirmed); err != nil {
56+
err = huh.NewConfirm().
57+
Title(confirmMsg).
58+
Value(&confirmed).
59+
Run()
60+
if err != nil {
6161
return err
6262
}
6363
if !confirmed {

go.mod

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ go 1.24.0
55
toolchain go1.24.10
66

77
require (
8-
github.com/AlecAivazis/survey/v2 v2.3.7
98
github.com/atotto/clipboard v0.1.4
109
github.com/briandowns/spinner v1.23.2
11-
github.com/charmbracelet/bubbles v0.21.0
10+
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7
1211
github.com/charmbracelet/bubbletea v1.3.10
12+
github.com/charmbracelet/huh v1.0.0
1313
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
1414
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be
1515
github.com/google/uuid v1.6.0
@@ -21,22 +21,24 @@ require (
2121
require (
2222
al.essio.dev/pkg/shellescape v1.5.1 // indirect
2323
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
24+
github.com/catppuccin/go v0.3.0 // indirect
2425
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
2526
github.com/charmbracelet/x/ansi v0.10.1 // indirect
2627
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
28+
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
2729
github.com/charmbracelet/x/term v0.2.1 // indirect
2830
github.com/danieljoos/wincred v1.2.2 // indirect
31+
github.com/dustin/go-humanize v1.0.1 // indirect
2932
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
3033
github.com/fatih/color v1.7.0 // indirect
3134
github.com/godbus/dbus/v5 v5.1.0 // indirect
3235
github.com/inconshreveable/mousetrap v1.1.0 // indirect
33-
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
3436
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
3537
github.com/mattn/go-colorable v0.1.2 // indirect
3638
github.com/mattn/go-isatty v0.0.20 // indirect
3739
github.com/mattn/go-localereader v0.0.1 // indirect
3840
github.com/mattn/go-runewidth v0.0.16 // indirect
39-
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
41+
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
4042
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
4143
github.com/muesli/cancelreader v0.2.2 // indirect
4244
github.com/muesli/termenv v0.16.0 // indirect

0 commit comments

Comments
 (0)