From 000d759ab85639787c5334d386b241bc83c344e5 Mon Sep 17 00:00:00 2001 From: "Customer.io Open Source Bot" Date: Wed, 27 May 2026 14:53:27 -0400 Subject: [PATCH] Add Customer.io CLI source CioCliPublicExport-RevId: 57a9d3daacc5b94a63c29cfb450df7b4192217e4 --- README.md | 16 ++++++++++++ cmd/root.go | 10 ++++++++ go.mod | 14 ++++++++++ go.sum | 31 ++++++++++++++++++++++ internal/tui/model.go | 60 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 131 insertions(+) create mode 100644 internal/tui/model.go diff --git a/README.md b/README.md index 196a0cd..aa9bb93 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,22 @@ To build from source instead: go install github.com/customerio/cli@latest ``` +## Uninstall + +```bash +# If installed via npm +npm uninstall -g @customerio/cli + +# If installed via go install +rm -f "$(go env GOPATH)/bin/cio" +``` + +To remove stored credentials and cached data: + +```bash +rm -rf ~/.cio +``` + ## Install the agent skill This repo ships a [SKILL.md](skills/cio/SKILL.md) so Claude Code, Cursor, Codex, Windsurf, and other agents that support [open agent skills](https://github.com/vercel-labs/skills) know how to drive the CLI. Install it with: diff --git a/cmd/root.go b/cmd/root.go index d75cdcf..a6f3a3e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,6 +9,7 @@ import ( "github.com/customerio/cli/internal/client" "github.com/customerio/cli/internal/output" + "github.com/customerio/cli/internal/tui" "github.com/customerio/cli/internal/useragent" "github.com/customerio/cli/internal/validate" "github.com/spf13/cobra" @@ -51,6 +52,15 @@ for direct token-based usage.`, } func init() { + defaultHelp := rootCmd.HelpFunc() + rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { + if cmd == rootCmd { + tui.RenderHelp(cmd.OutOrStdout()) + return + } + defaultHelp(cmd, args) + }) + flags := rootCmd.PersistentFlags() flags.String("json", "", "Raw JSON request body or @filename to read from file") diff --git a/go.mod b/go.mod index 7c3cb9a..e72ceb0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/customerio/cli go 1.26.1 require ( + github.com/charmbracelet/lipgloss v1.1.0 github.com/itchyny/gojq v0.12.18 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.9 @@ -11,6 +12,19 @@ require ( ) require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/x/ansi v0.10.1 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/itchyny/timefmt-go v0.1.7 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect ) diff --git a/go.sum b/go.sum index b2e20dd..01de081 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,19 @@ +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/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/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.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= +github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= +github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -5,12 +21,27 @@ github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc= github.com/itchyny/gojq v0.12.18/go.mod h1:4hPoZ/3lN9fDL1D+aK7DY1f39XZpY9+1Xpjz8atrEkg= github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA= github.com/itchyny/timefmt-go v0.1.7/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +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-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +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/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= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= diff --git a/internal/tui/model.go b/internal/tui/model.go new file mode 100644 index 0000000..e168c91 --- /dev/null +++ b/internal/tui/model.go @@ -0,0 +1,60 @@ +package tui + +import ( + "io" + "os" + "strings" + + "github.com/charmbracelet/lipgloss" +) + +const logoText = ` ██████╗ ██╗ ██████╗ +██╔════╝ ██║ ██╔═══██╗ +██║ ██║ ██║ ██║ +██║ ██║ ██║ ██║ +╚██████╗ ██║ ╚██████╔╝ + ╚═════╝ ╚═╝ ╚═════╝` + +type command struct { + name string + desc string +} + +// RenderHelp writes the branded help screen to w. +func RenderHelp(w io.Writer) { + r := lipgloss.NewRenderer(os.Stdout) + + logo := r.NewStyle().Foreground(lipgloss.Color("#7FE07F")) + tag := r.NewStyle().Foreground(lipgloss.Color("#FFFFFF")) + header := r.NewStyle().Foreground(lipgloss.Color("#FFFFFF")).Bold(true) + cmd := r.NewStyle().Foreground(lipgloss.Color("#7FE07F")) + desc := r.NewStyle().Foreground(lipgloss.Color("#FFFFFF")) + + var b strings.Builder + + b.WriteString(logo.Render(logoText)) + b.WriteString("\n\n") + b.WriteString(tag.Render("» Customer.io CLI")) + b.WriteString("\n\n") + + b.WriteString(header.Render("Get started:") + "\n") + for _, c := range []command{ + {"cio auth login", "Authenticate with your workspace"}, + {"cio auth signup", "Create a new account"}, + } { + b.WriteString(" " + cmd.Render(c.name) + " " + desc.Render(c.desc) + "\n") + } + b.WriteString("\n") + + b.WriteString(header.Render("Common commands:") + "\n") + for _, c := range []command{ + {"cio api ", "Make an API request"}, + {"cio send email", "Send a one-off email"}, + {"cio domains list", "List sending domains"}, + {"cio --help", "Get help for a command"}, + } { + b.WriteString(" " + cmd.Render(c.name) + " " + desc.Render(c.desc) + "\n") + } + + io.WriteString(w, b.String()) +}