diff --git a/cmd/health.go b/cmd/health.go
index a82dc7d..6d13025 100644
--- a/cmd/health.go
+++ b/cmd/health.go
@@ -26,10 +26,11 @@ import (
"github.com/urfave/cli/v3"
)
-func Health(conf *cfg.Config) *cli.Command {
+func Status(conf *cfg.Config) *cli.Command {
return &cli.Command{
- Name: "health",
- Usage: "show ES health",
+ Name: "status",
+ Usage: "show ES status",
+ Aliases: []string{"s"},
Action: func(ctx context.Context, cmd *cli.Command) error {
if err := es.Health(conf); err != nil {
return err
diff --git a/cmd/index.go b/cmd/index.go
new file mode 100644
index 0000000..e5a83ad
--- /dev/null
+++ b/cmd/index.go
@@ -0,0 +1,63 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package cmd
+
+import (
+ "context"
+
+ "codeberg.org/scip/esctl/pkg/cfg"
+ "codeberg.org/scip/esctl/pkg/es"
+
+ "github.com/urfave/cli/v3"
+)
+
+func Index(conf *cfg.Config) *cli.Command {
+ return &cli.Command{
+ Name: "index",
+ Aliases: []string{"i"},
+ Usage: "manage indicies",
+
+ Commands: []*cli.Command{
+ IndexList(conf),
+ },
+ }
+}
+
+func IndexList(conf *cfg.Config) *cli.Command {
+ return &cli.Command{
+ Name: "list",
+ Aliases: []string{"ls"},
+ Usage: "list indicies",
+
+ Flags: []cli.Flag{
+ &cli.IntFlag{
+ Name: "max",
+ Usage: "max number of items to process",
+ Destination: &conf.MaxItems,
+ Aliases: []string{"m"},
+ },
+ },
+
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ if err := es.IndexList(conf); err != nil {
+ return err
+ }
+
+ return nil
+ },
+ }
+}
diff --git a/cmd/root.go b/cmd/root.go
index f66f6c9..04b9f52 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -60,8 +60,9 @@ func Main() int {
},
Commands: []*cli.Command{
- Health(conf),
+ Status(conf),
Search(conf),
+ Index(conf),
},
Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
diff --git a/cmd/search.go b/cmd/search.go
index 0bfc3d2..76b8453 100644
--- a/cmd/search.go
+++ b/cmd/search.go
@@ -38,6 +38,7 @@ func Search(conf *cfg.Config) *cli.Command {
Usage: "index to search within",
Sources: cli.EnvVars("ES_INDEX"),
Destination: &conf.Index,
+ Aliases: []string{"i"},
},
&cli.IntFlag{
Name: "from",
diff --git a/go.mod b/go.mod
index d1f0f75..55f7cbd 100644
--- a/go.mod
+++ b/go.mod
@@ -1,37 +1,45 @@
// Copyright © 2026 Thomas von Dein
-//
+//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-//
+//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
-//
+//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
module codeberg.org/scip/esctl
-go 1.24
+go 1.25.0
require (
github.com/alecthomas/repr v0.5.2 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/clipperhouse/displaywidth v0.10.0 // indirect
+ github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
github.com/elastic/elastic-transport-go/v8 v8.11.0 // indirect
github.com/elastic/go-elasticsearch/v9 v9.3.2 // indirect
- github.com/fatih/color v1.16.0 // indirect
+ github.com/fatih/color v1.19.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/lmittmann/tint v1.1.3 // indirect
- github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.21 // indirect
+ github.com/mattn/go-runewidth v0.0.19 // indirect
+ github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
+ github.com/olekukonko/errors v1.2.0 // indirect
+ github.com/olekukonko/ll v0.1.6 // indirect
+ github.com/olekukonko/tablewriter v1.1.4 // indirect
github.com/tlinden/yadu v0.1.3 // indirect
github.com/urfave/cli/v3 v3.8.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
- golang.org/x/sys v0.28.0 // indirect
+ golang.org/x/sys v0.42.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 2306ef2..d4e1dd4 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,11 @@
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g=
+github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs=
+github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos=
+github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/elastic/elastic-transport-go/v8 v8.9.0 h1:KeT/2P54F0xS0S8Y3Pf+tFDg4HmBgReQMB+BMz8dDAs=
github.com/elastic/elastic-transport-go/v8 v8.9.0/go.mod h1:ssMTvNS2hwf7CaiGsRRsx4gQHFZ/jS/DkLcISxekWzc=
github.com/elastic/elastic-transport-go/v8 v8.11.0 h1:taYmqC2M6+fZt/+W+ENYh/W5L9+KrlJGOSbEJs8egWc=
@@ -8,6 +14,10 @@ github.com/elastic/go-elasticsearch/v9 v9.3.2 h1:nvtvfN/Gsp/rzUPz/9yILwDAsYJ3s5L
github.com/elastic/go-elasticsearch/v9 v9.3.2/go.mod h1:ubKUMJCJbX5V/gW5MIn2NQZyaEZ61ubXwJmD5UMNrM8=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+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/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
+github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -17,9 +27,21 @@ github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I=
github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
+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/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
+github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
+github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
+github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
+github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA=
+github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88=
+github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
+github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
github.com/tlinden/yadu v0.1.3 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY=
github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA=
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
@@ -35,6 +57,10 @@ go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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=
diff --git a/pkg/cfg/config.go b/pkg/cfg/config.go
index e21a6ba..19fac7a 100644
--- a/pkg/cfg/config.go
+++ b/pkg/cfg/config.go
@@ -31,12 +31,12 @@ const (
)
type Config struct {
- Uri, User, Pass string
- ES *elasticsearch.TypedClient
- Debug bool
- From, To int
- Index string
- Filter []string
+ Uri, User, Pass string
+ ES *elasticsearch.TypedClient
+ Debug bool
+ From, To, MaxItems int
+ Index string
+ Filter []string
}
func Init() (*Config, error) {
diff --git a/pkg/es/aux.go b/pkg/es/aux.go
index 05fe721..cb4e16c 100644
--- a/pkg/es/aux.go
+++ b/pkg/es/aux.go
@@ -18,6 +18,7 @@ package es
import (
"context"
+ "fmt"
"log"
"log/slog"
@@ -30,7 +31,20 @@ func Health(conf *cfg.Config) error {
log.Fatalf("Error getting health: %s", err)
}
- slog.Info("ES result", "cluster health", res)
+ slog.Debug("ES result", "cluster health", res)
+ table := NewTable(5)
+
+ table.headers = []string{bold("SETTING"), bold("STATUS")}
+
+ table.entries = [][]string{
+ {"Cluster Name", Colorize(*&res.Status.Name, res.ClusterName)},
+ {"Active Shards", fmt.Sprintf("%d", res.ActiveShards)},
+ {"Active Primary Shards", fmt.Sprintf("%d", res.ActivePrimaryShards)},
+ {"Indicies", fmt.Sprintf("%d", len(res.Indices))},
+ {"Nodes", fmt.Sprintf("%d", res.NumberOfNodes)},
+ }
+
+ table.PrintMarkdown()
return nil
}
diff --git a/pkg/es/color.go b/pkg/es/color.go
new file mode 100644
index 0000000..930b208
--- /dev/null
+++ b/pkg/es/color.go
@@ -0,0 +1,40 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+package es
+
+import "github.com/fatih/color"
+
+var (
+ green = color.New(color.BgGreen, color.FgHiWhite).SprintFunc()
+ orange = color.BgRGB(255, 128, 0).AddRGB(0, 0, 0).SprintFunc()
+ red = color.New(color.BgRed, color.FgHiWhite).SprintFunc()
+ bold = color.New(color.Bold).SprintFunc()
+)
+
+func Colorize(col, what string) string {
+ switch col {
+ case "green":
+ what = green(what)
+ case "orange":
+ what = orange(what)
+ case "red":
+ what = red(what)
+ }
+
+ return what
+}
diff --git a/pkg/es/index.go b/pkg/es/index.go
new file mode 100644
index 0000000..4f64b22
--- /dev/null
+++ b/pkg/es/index.go
@@ -0,0 +1,61 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package es
+
+import (
+ "context"
+ "log"
+ "log/slog"
+
+ "codeberg.org/scip/esctl/pkg/cfg"
+)
+
+func IndexList(conf *cfg.Config) error {
+ res, err := conf.ES.Cat.Indices().Do(context.Background())
+ if err != nil {
+ log.Fatalf("Error getting indicies: %s", err)
+ }
+
+ slog.Debug("ES result", "indicies", res)
+
+ size := len(res)
+
+ if conf.MaxItems > 0 {
+ if size > conf.MaxItems {
+ size = conf.MaxItems
+ }
+ }
+
+ table := NewTable(size)
+
+ table.headers = []string{bold("NAME"), bold("SIZE"), bold("DOCSCOUNT")}
+
+ for idx, index := range res {
+ name := Colorize(*index.Health, *index.Index)
+
+ table.entries[idx] = []string{name, *index.DatasetSize, *index.DocsCount}
+
+ if idx == size-1 {
+ break
+ }
+ }
+
+ table.Sort()
+ table.PrintMarkdown()
+
+ return nil
+}
diff --git a/pkg/es/search.go b/pkg/es/search.go
index fe7b76b..e085a92 100644
--- a/pkg/es/search.go
+++ b/pkg/es/search.go
@@ -19,6 +19,7 @@ package es
import (
"context"
"fmt"
+ "log/slog"
"strings"
"codeberg.org/scip/esctl/pkg/cfg"
@@ -64,6 +65,8 @@ func Search(conf *cfg.Config, q string) error {
return fmt.Errorf("Error running search (esdsl): %s", err)
}
+ slog.Debug("ES result", "search", res)
+
for _, hit := range res.Hits.Hits {
fmt.Printf("%s\n", hit.Source_)
}
diff --git a/pkg/es/table.go b/pkg/es/table.go
new file mode 100644
index 0000000..28fe3a5
--- /dev/null
+++ b/pkg/es/table.go
@@ -0,0 +1,108 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package es
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "github.com/olekukonko/tablewriter"
+ "github.com/olekukonko/tablewriter/renderer"
+ "github.com/olekukonko/tablewriter/tw"
+)
+
+type Table struct {
+ headers []string
+ entries [][]string
+}
+
+func NewTable(size int) *Table {
+ table := Table{}
+
+ table.headers = make([]string, size)
+ table.entries = make([][]string, size)
+
+ return &table
+}
+
+func (data *Table) PrintMarkdown() error {
+ tableString := &strings.Builder{}
+
+ table := tablewriter.NewTable(tableString,
+ tablewriter.WithRenderer(
+ renderer.NewBlueprint(
+ tw.Rendition{
+ Borders: tw.Border{
+ Left: tw.On,
+ Right: tw.On,
+ Top: tw.Off,
+ Bottom: tw.Off,
+ },
+ Settings: tw.Settings{
+ Separators: tw.Separators{
+ ShowHeader: tw.On,
+ ShowFooter: tw.Off,
+ BetweenRows: tw.Off,
+ BetweenColumns: 0,
+ },
+ },
+ Symbols: tw.NewSymbols(tw.StyleMarkdown),
+ })),
+
+ tablewriter.WithConfig(
+ tablewriter.Config{
+ Header: tw.CellConfig{
+ Formatting: tw.CellFormatting{
+ Alignment: tw.AlignLeft,
+ AutoFormat: tw.Off,
+ },
+ },
+ Row: tw.CellConfig{
+ Formatting: tw.CellFormatting{
+ Alignment: tw.AlignLeft,
+ },
+ },
+ },
+ ),
+ )
+
+ table.Header(data.headers)
+
+ if err := table.Bulk(data.entries); err != nil {
+ return fmt.Errorf("Failed to add data to table renderer: %s", err)
+ }
+
+ if err := table.Render(); err != nil {
+ fmt.Errorf("Failed to render table: %s", err)
+ }
+
+ fmt.Println(tableString.String())
+
+ return nil
+}
+
+func (data *Table) Sort() {
+ // sanity checks
+ if len(data.entries) == 0 {
+ return
+ }
+
+ sort.Slice(data.entries, func(i, j int) bool {
+ return data.entries[i][0] < data.entries[j][0]
+ })
+}