diff --git a/go.mod b/go.mod index b64e2e2..19bf099 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,9 @@ go 1.26.0 toolchain go1.26.3 require ( + charm.land/huh/v2 v2.0.3 + charm.land/lipgloss/v2 v2.0.3 + github.com/arran4/golang-ical v0.3.5 github.com/atotto/clipboard v0.1.4 github.com/fatih/color v1.18.0 github.com/gdamore/tcell/v2 v2.13.4 @@ -13,8 +16,10 @@ require ( github.com/rivo/tview v0.42.0 github.com/slack-go/slack v0.17.3 github.com/spf13/cobra v1.8.1 + github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.11.1 github.com/zalando/go-keyring v0.2.6 + golang.org/x/crypto v0.46.0 golang.org/x/mod v0.30.0 golang.org/x/term v0.38.0 golang.org/x/text v0.32.0 @@ -24,43 +29,38 @@ require ( require ( al.essio.dev/pkg/shellescape v1.6.0 // indirect - github.com/arran4/golang-ical v0.3.5 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + charm.land/bubbles/v2 v2.0.0 // indirect + charm.land/bubbletea/v2 v2.0.2 // indirect github.com/catppuccin/go v0.3.0 // indirect - github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect - github.com/charmbracelet/bubbletea v1.3.6 // indirect - github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/huh v1.0.0 // indirect - github.com/charmbracelet/lipgloss v1.1.0 // indirect - github.com/charmbracelet/x/ansi v0.9.3 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect + github.com/charmbracelet/x/ansi v0.11.7 // indirect + github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect - github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/charmbracelet/x/termios v0.1.1 // indirect + github.com/charmbracelet/x/windows v0.2.2 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/danieljoos/wincred v1.2.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/gdamore/encoding v1.0.1 // indirect github.com/godbus/dbus/v5 v5.2.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/lucasb-eyer/go-colorful v1.3.0 // indirect + github.com/lucasb-eyer/go-colorful v1.4.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.23 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/termenv v0.16.0 // indirect github.com/ncruces/julianday v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/tetratelabs/wazero v1.11.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/crypto v0.46.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.39.0 // indirect + golang.org/x/sys v0.43.0 // indirect lukechampine.com/adiantum v1.1.1 // indirect ) diff --git a/go.sum b/go.sum index fd9e22d..bbacaee 100644 --- a/go.sum +++ b/go.sum @@ -1,40 +1,60 @@ al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA= al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= +charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s= +charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI= +charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0= +charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ= +charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU= +charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc= +charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= +charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/arran4/golang-ical v0.3.5 h1:bbz6ld4dC+MmCKiFfOd6SkmIGnhNMBACZ485ULh7p9A= github.com/arran4/golang-ical v0.3.5/go.mod h1:OnguFgjN0Hmx8jzpmWcC+AkHio94ujmLHKoaef7xQh8= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= +github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= -github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws= -github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw= -github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU= -github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= -github.com/charmbracelet/huh v1.0.0 h1:wOnedH8G4qzJbmhftTqrpppyqHakl/zbbNdXIWJyIxw= -github.com/charmbracelet/huh v1.0.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0= -github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= -github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= -github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= +github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA= +github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98= +github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= +github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= +github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs= +github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= +github.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE= +github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= -github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= -github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= +github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= +github.com/charmbracelet/x/xpty v0.1.3 h1:eGSitii4suhzrISYH50ZfufV3v085BXQwIytcOdFSsw= +github.com/charmbracelet/x/xpty v0.1.3/go.mod h1:poPYpWuLDBFCKmKLDnhBp51ATa0ooD8FhypRwEFtH3Y= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= @@ -45,6 +65,8 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/godbus/dbus/v5 v5.2.1 h1:I4wwMdWSkmI57ewd+elNGwLRf2/dtSaFz1DujfWYvOk= github.com/godbus/dbus/v5 v5.2.1/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -53,33 +75,30 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= -github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= +github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/ncruces/go-sqlite3 v0.30.4 h1:j9hEoOL7f9ZoXl8uqXVniaq1VNwlWAXihZbTvhqPPjA= github.com/ncruces/go-sqlite3 v0.30.4/go.mod h1:7WR20VSC5IZusKhUdiR9y1NsUqnZgqIYCmKKoMEYg68= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/tview v0.42.0 h1:b/ftp+RxtDsHSaynXTbJb+/n/BxDEi+W3UfF5jILK6c= github.com/rivo/tview v0.42.0/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -104,6 +123,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= @@ -120,13 +141,12 @@ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -146,9 +166,9 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA= diff --git a/internal/adapters/dashboard/gateway_client.go b/internal/adapters/dashboard/gateway_client.go index 55384ac..6ed183f 100644 --- a/internal/adapters/dashboard/gateway_client.go +++ b/internal/adapters/dashboard/gateway_client.go @@ -91,7 +91,8 @@ func (c *GatewayClient) CreateApplication(ctx context.Context, orgPublicID, regi variables := map[string]any{ "orgPublicId": orgPublicID, "options": map[string]any{ - "region": region, + "region": region, + "environment": "sandbox", "branding": map[string]any{ "name": name, }, diff --git a/internal/adapters/dashboard/gateway_client_test.go b/internal/adapters/dashboard/gateway_client_test.go index baec5c9..e881962 100644 --- a/internal/adapters/dashboard/gateway_client_test.go +++ b/internal/adapters/dashboard/gateway_client_test.go @@ -68,6 +68,7 @@ func TestGatewayClientOperations(t *testing.T) { assert.Equal(t, "org-1", variables["orgPublicId"]) options := variables["options"].(map[string]any) assert.Equal(t, "eu", options["region"]) + assert.Equal(t, "sandbox", options["environment"]) branding := options["branding"].(map[string]any) assert.Equal(t, "Created App", branding["name"]) @@ -78,7 +79,7 @@ func TestGatewayClientOperations(t *testing.T) { "clientSecret": "secret", "organizationId": "org-1", "region": "eu", - "environment": "production", + "environment": "sandbox", "branding": map[string]any{ "name": "Created App", }, diff --git a/internal/adapters/nylas/agent_test.go b/internal/adapters/nylas/agent_test.go index dc1ca29..875b84d 100644 --- a/internal/adapters/nylas/agent_test.go +++ b/internal/adapters/nylas/agent_test.go @@ -41,6 +41,47 @@ func TestParseError_ParsesTopLevelMessage(t *testing.T) { assert.Equal(t, "extra fields not permitted: app_password", apiErr.Message) } +func TestParseError_ParsesOAuthErrorFormat(t *testing.T) { + client := NewHTTPClient() + + resp := &http.Response{ + StatusCode: http.StatusBadRequest, + Body: io.NopCloser(strings.NewReader(`{ + "error": "invalid_grant", + "error_code": 45004, + "error_description": "Code verifier challenge failed", + "error_uri": "https://developer.nylas.com/docs/api/errors/" + }`)), + } + + err := client.parseError(resp) + require.Error(t, err) + + var apiErr *domain.APIError + require.ErrorAs(t, err, &apiErr) + assert.Equal(t, http.StatusBadRequest, apiErr.StatusCode) + assert.Equal(t, "invalid_grant", apiErr.Type) + assert.Equal(t, "Code verifier challenge failed", apiErr.Message) +} + +func TestParseError_FallsBackToStatusCodeWhenBodyEmpty(t *testing.T) { + client := NewHTTPClient() + + resp := &http.Response{ + StatusCode: http.StatusInternalServerError, + Body: io.NopCloser(strings.NewReader("")), + } + + err := client.parseError(resp) + require.Error(t, err) + + var apiErr *domain.APIError + require.ErrorAs(t, err, &apiErr) + assert.Equal(t, http.StatusInternalServerError, apiErr.StatusCode) + assert.Empty(t, apiErr.Type) + assert.Empty(t, apiErr.Message) +} + func TestListAgentAccounts(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/v3/grants", r.URL.Path) diff --git a/internal/adapters/nylas/client.go b/internal/adapters/nylas/client.go index 1f53832..e396cd8 100644 --- a/internal/adapters/nylas/client.go +++ b/internal/adapters/nylas/client.go @@ -124,8 +124,12 @@ func (c *HTTPClient) setAuthHeader(req *http.Request) { // Uses streaming decoder with size limit to avoid large allocations. func (c *HTTPClient) parseError(resp *http.Response) error { // Limit error response body to 10KB to prevent memory issues - limitedReader := io.LimitReader(resp.Body, 10*1024) + body, err := io.ReadAll(io.LimitReader(resp.Body, 10*1024)) + if err != nil || len(body) == 0 { + return &domain.APIError{StatusCode: resp.StatusCode} + } + // Try Nylas standard format: {"error": {"message": "...", "type": "..."}} var errResp struct { Message string `json:"message"` Type string `json:"type"` @@ -134,9 +138,7 @@ func (c *HTTPClient) parseError(resp *http.Response) error { Type string `json:"type"` } `json:"error"` } - - // Use streaming decoder instead of ReadAll + Unmarshal - if err := json.NewDecoder(limitedReader).Decode(&errResp); err == nil { + if err := json.Unmarshal(body, &errResp); err == nil { message := strings.TrimSpace(errResp.Error.Message) errType := strings.TrimSpace(errResp.Error.Type) if message == "" { @@ -154,6 +156,23 @@ func (c *HTTPClient) parseError(resp *http.Response) error { } } + // Try OAuth format: {"error": "...", "error_description": "..."} + var oauthErr struct { + Error string `json:"error"` + Description string `json:"error_description"` + } + if err := json.Unmarshal(body, &oauthErr); err == nil && oauthErr.Error != "" { + message := oauthErr.Description + if message == "" { + message = oauthErr.Error + } + return &domain.APIError{ + StatusCode: resp.StatusCode, + Type: oauthErr.Error, + Message: message, + } + } + return &domain.APIError{StatusCode: resp.StatusCode} } diff --git a/internal/app/auth/service.go b/internal/app/auth/service.go index aa95a4e..79f8aef 100644 --- a/internal/app/auth/service.go +++ b/internal/app/auth/service.go @@ -230,8 +230,10 @@ func generatePKCEPair() (string, string, error) { return "", "", err } + // Nylas uses base64(hex(sha256(verifier))) instead of RFC 7636 base64url(sha256(verifier)) hash := sha256.Sum256([]byte(verifier)) - challenge := base64.RawURLEncoding.EncodeToString(hash[:]) + hexHash := fmt.Sprintf("%x", hash) + challenge := base64.RawStdEncoding.EncodeToString([]byte(hexHash)) return verifier, challenge, nil } diff --git a/internal/app/auth/service_test.go b/internal/app/auth/service_test.go index eb0037f..4d36216 100644 --- a/internal/app/auth/service_test.go +++ b/internal/app/auth/service_test.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/base64" "errors" + "fmt" "testing" "github.com/nylas/cli/internal/adapters/nylas" @@ -746,5 +747,6 @@ func TestService_RemoveLocalGrant(t *testing.T) { func pkceChallenge(verifier string) string { hash := sha256.Sum256([]byte(verifier)) - return base64.RawURLEncoding.EncodeToString(hash[:]) + hexHash := fmt.Sprintf("%x", hash) + return base64.RawStdEncoding.EncodeToString([]byte(hexHash)) } diff --git a/internal/cli/common/colors.go b/internal/cli/common/colors.go index a4a4bc7..aedfdcd 100644 --- a/internal/cli/common/colors.go +++ b/internal/cli/common/colors.go @@ -1,7 +1,7 @@ package common import ( - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" "github.com/fatih/color" ) diff --git a/internal/cli/common/prompt.go b/internal/cli/common/prompt.go index 1751b3f..21f4f34 100644 --- a/internal/cli/common/prompt.go +++ b/internal/cli/common/prompt.go @@ -4,7 +4,7 @@ package common import ( "os" - "github.com/charmbracelet/huh" + "charm.land/huh/v2" "golang.org/x/term" ) @@ -36,12 +36,13 @@ func Select[T comparable](title string, options []SelectOption[T]) (T, error) { huhOpts[i] = huh.NewOption(opt.Label, opt.Value) } - err := huh.NewSelect[T](). - Title(title). - Options(huhOpts...). - Value(&result). - WithTheme(theme). - Run() + err := huh.Run( + huh.NewSelect[T](). + Title(title). + Options(huhOpts...). + Value(&result). + WithTheme(theme), + ) return result, err } @@ -53,13 +54,14 @@ func ConfirmPrompt(title string, defaultYes bool) (bool, error) { } result := defaultYes - err := huh.NewConfirm(). - Title(title). - Affirmative("Yes"). - Negative("No"). - Value(&result). - WithTheme(theme). - Run() + err := huh.Run( + huh.NewConfirm(). + Title(title). + Affirmative("Yes"). + Negative("No"). + Value(&result). + WithTheme(theme), + ) return result, err } @@ -79,7 +81,7 @@ func InputPrompt(title, placeholder string) (string, error) { field = field.Placeholder(placeholder) } - err := field.WithTheme(theme).Run() + err := huh.Run(field.WithTheme(theme)) if err != nil { return "", err } @@ -96,12 +98,13 @@ func PasswordPrompt(title string) (string, error) { } var result string - err := huh.NewInput(). - Title(title). - EchoMode(huh.EchoModePassword). - Value(&result). - WithTheme(theme). - Run() + err := huh.Run( + huh.NewInput(). + Title(title). + EchoMode(huh.EchoModePassword). + Value(&result). + WithTheme(theme), + ) return result, err } diff --git a/internal/cli/common/theme.go b/internal/cli/common/theme.go index 55b4ec0..4032279 100644 --- a/internal/cli/common/theme.go +++ b/internal/cli/common/theme.go @@ -1,118 +1,122 @@ package common import ( - "github.com/charmbracelet/huh" - "github.com/charmbracelet/lipgloss" + "image/color" + + "charm.land/huh/v2" + "charm.land/lipgloss/v2" ) // Nylas brand color palette — used consistently across all CLI output. -const ( - ColorPrimary = lipgloss.Color("#4169E1") // Royal Blue — brand accent - ColorSuccess = lipgloss.Color("#4CAF50") // Green - ColorWarning = lipgloss.Color("#FFC107") // Amber - ColorError = lipgloss.Color("#F44336") // Red - ColorMuted = lipgloss.Color("#6B7280") // Gray - ColorText = lipgloss.Color("#E0E0E0") // Light gray - ColorDim = lipgloss.Color("#4A4A4A") // Dark gray +var ( + ColorPrimary color.Color = lipgloss.Color("#4169E1") // Royal Blue — brand accent + ColorSuccess color.Color = lipgloss.Color("#4CAF50") // Green + ColorWarning color.Color = lipgloss.Color("#FFC107") // Amber + ColorError color.Color = lipgloss.Color("#F44336") // Red + ColorMuted color.Color = lipgloss.Color("#6B7280") // Gray + ColorText color.Color = lipgloss.Color("#E0E0E0") // Light gray + ColorDim color.Color = lipgloss.Color("#4A4A4A") // Dark gray ) // NylasTheme returns the huh theme used for all interactive prompts. -func NylasTheme() *huh.Theme { - t := huh.ThemeBase() - - // Focused field styles - t.Focused.Base = lipgloss.NewStyle(). - PaddingLeft(1). - BorderStyle(lipgloss.ThickBorder()). - BorderLeft(true). - BorderForeground(ColorPrimary) - - t.Focused.Title = lipgloss.NewStyle(). - Foreground(ColorPrimary). - Bold(true) - - t.Focused.Description = lipgloss.NewStyle(). - Foreground(ColorMuted) - - t.Focused.ErrorIndicator = lipgloss.NewStyle(). - Foreground(ColorError). - SetString(" *") - - t.Focused.ErrorMessage = lipgloss.NewStyle(). - Foreground(ColorError) - - // Select - t.Focused.SelectSelector = lipgloss.NewStyle(). - Foreground(ColorPrimary). - SetString("❯ ") - - t.Focused.Option = lipgloss.NewStyle(). - Foreground(ColorText) - - t.Focused.NextIndicator = lipgloss.NewStyle(). - Foreground(ColorMuted). - SetString(" →") - - t.Focused.PrevIndicator = lipgloss.NewStyle(). - Foreground(ColorMuted). - SetString("← ") - - // MultiSelect - t.Focused.MultiSelectSelector = lipgloss.NewStyle(). - Foreground(ColorPrimary). - SetString("❯ ") - - t.Focused.SelectedOption = lipgloss.NewStyle(). - Foreground(ColorSuccess) - - t.Focused.SelectedPrefix = lipgloss.NewStyle(). - Foreground(ColorSuccess). - SetString("✓ ") - - t.Focused.UnselectedOption = lipgloss.NewStyle(). - Foreground(ColorText) - - t.Focused.UnselectedPrefix = lipgloss.NewStyle(). - Foreground(ColorMuted). - SetString("○ ") - - // Confirm buttons - t.Focused.FocusedButton = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FFFFFF")). - Background(ColorPrimary). - Padding(0, 2). - Bold(true) - - t.Focused.BlurredButton = lipgloss.NewStyle(). - Foreground(ColorMuted). - Background(ColorDim). - Padding(0, 2) - - // Text input - t.Focused.TextInput.Cursor = lipgloss.NewStyle(). - Foreground(ColorPrimary) - - t.Focused.TextInput.Placeholder = lipgloss.NewStyle(). - Foreground(ColorMuted) - - t.Focused.TextInput.Prompt = lipgloss.NewStyle(). - Foreground(ColorPrimary). - SetString("❯ ") - - t.Focused.TextInput.Text = lipgloss.NewStyle(). - Foreground(ColorText) - - // Card / Note - t.Focused.Card = t.Focused.Base - t.Focused.NoteTitle = t.Focused.Title - t.Focused.Next = t.Focused.FocusedButton - - // Blurred state — same styles but hidden border - t.Blurred = t.Focused - t.Blurred.Base = t.Focused.Base.BorderStyle(lipgloss.HiddenBorder()) - t.Blurred.Card = t.Blurred.Base - t.Blurred.NextIndicator = lipgloss.NewStyle() - t.Blurred.PrevIndicator = lipgloss.NewStyle() - - return t +func NylasTheme() huh.Theme { + return huh.ThemeFunc(func(isDark bool) *huh.Styles { + t := huh.ThemeBase(isDark) + + // Focused field styles + t.Focused.Base = lipgloss.NewStyle(). + PaddingLeft(1). + BorderStyle(lipgloss.ThickBorder()). + BorderLeft(true). + BorderForeground(ColorPrimary) + + t.Focused.Title = lipgloss.NewStyle(). + Foreground(ColorPrimary). + Bold(true) + + t.Focused.Description = lipgloss.NewStyle(). + Foreground(ColorMuted) + + t.Focused.ErrorIndicator = lipgloss.NewStyle(). + Foreground(ColorError). + SetString(" *") + + t.Focused.ErrorMessage = lipgloss.NewStyle(). + Foreground(ColorError) + + // Select + t.Focused.SelectSelector = lipgloss.NewStyle(). + Foreground(ColorPrimary). + SetString("❯ ") + + t.Focused.Option = lipgloss.NewStyle(). + Foreground(ColorText) + + t.Focused.NextIndicator = lipgloss.NewStyle(). + Foreground(ColorMuted). + SetString(" →") + + t.Focused.PrevIndicator = lipgloss.NewStyle(). + Foreground(ColorMuted). + SetString("← ") + + // MultiSelect + t.Focused.MultiSelectSelector = lipgloss.NewStyle(). + Foreground(ColorPrimary). + SetString("❯ ") + + t.Focused.SelectedOption = lipgloss.NewStyle(). + Foreground(ColorSuccess) + + t.Focused.SelectedPrefix = lipgloss.NewStyle(). + Foreground(ColorSuccess). + SetString("✓ ") + + t.Focused.UnselectedOption = lipgloss.NewStyle(). + Foreground(ColorText) + + t.Focused.UnselectedPrefix = lipgloss.NewStyle(). + Foreground(ColorMuted). + SetString("○ ") + + // Confirm buttons + t.Focused.FocusedButton = lipgloss.NewStyle(). + Foreground(lipgloss.Color("#FFFFFF")). + Background(ColorPrimary). + Padding(0, 2). + Bold(true) + + t.Focused.BlurredButton = lipgloss.NewStyle(). + Foreground(ColorMuted). + Background(ColorDim). + Padding(0, 2) + + // Text input + t.Focused.TextInput.Cursor = lipgloss.NewStyle(). + Foreground(ColorPrimary) + + t.Focused.TextInput.Placeholder = lipgloss.NewStyle(). + Foreground(ColorMuted) + + t.Focused.TextInput.Prompt = lipgloss.NewStyle(). + Foreground(ColorPrimary). + SetString("❯ ") + + t.Focused.TextInput.Text = lipgloss.NewStyle(). + Foreground(ColorText) + + // Card / Note + t.Focused.Card = t.Focused.Base + t.Focused.NoteTitle = t.Focused.Title + t.Focused.Next = t.Focused.FocusedButton + + // Blurred state — same styles but hidden border + t.Blurred = t.Focused + t.Blurred.Base = t.Focused.Base.BorderStyle(lipgloss.HiddenBorder()) + t.Blurred.Card = t.Blurred.Base + t.Blurred.NextIndicator = lipgloss.NewStyle() + t.Blurred.PrevIndicator = lipgloss.NewStyle() + + return t + }) } diff --git a/internal/cli/dashboard/sso.go b/internal/cli/dashboard/sso.go index 8808867..206d2f3 100644 --- a/internal/cli/dashboard/sso.go +++ b/internal/cli/dashboard/sso.go @@ -1,6 +1,7 @@ package dashboard import ( + "bufio" "context" "fmt" "os" @@ -101,20 +102,22 @@ func runSSO(provider, mode string, privacyPolicyAccepted bool, orgPublicIDs ...s return wrapDashboardError(err) } - // Show the URL and code + // Show the URL and code, then wait for user to press Enter before opening browser url := resp.VerificationURIComplete if url == "" { url = resp.VerificationURI } fmt.Println() - _, _ = common.BoldCyan.Printf(" Open: %s\n", url) - if resp.UserCode != "" && resp.VerificationURIComplete == "" { - _, _ = common.Bold.Printf(" Code: %s\n", resp.UserCode) + if resp.UserCode != "" { + _, _ = common.Bold.Printf(" First, copy your one-time code: %s\n", resp.UserCode) } + _, _ = common.BoldCyan.Printf(" Open: %s\n", url) fmt.Println() - // Try to open browser + _, _ = common.Dim.Print(" Press Enter to open the browser...") + _, _ = bufio.NewReader(os.Stdin).ReadString('\n') + b := browser.NewDefaultBrowser() if openErr := b.Open(url); openErr == nil { _, _ = common.Dim.Println(" Browser opened. Complete sign-in there.")