@@ -4,12 +4,14 @@ import (
44 "fmt"
55 "io"
66 "math/rand"
7+ "sort"
78 "strings"
89 "time"
910
1011 "github.com/spf13/cobra"
1112
1213 "go.datum.net/datumctl/internal/datumconfig"
14+ "go.datum.net/datumctl/internal/pluginstore"
1315)
1416
1517// runLanding prints a contextual welcome when `datumctl` is invoked with no
@@ -113,19 +115,81 @@ func printLoggedInLanding(out io.Writer, cfg *datumconfig.ConfigV1Beta1, session
113115 fmt .Fprintln (out , " datumctl describe <resource> <name>" )
114116 fmt .Fprintln (out , " Ship something datumctl apply -f file.yaml" )
115117 fmt .Fprintln (out , " datumctl create <resource> ..." )
118+ fmt .Fprintln (out , " Extend datumctl datumctl plugin browse" )
116119 fmt .Fprintln (out , " Explore the API datumctl api-resources" )
117120 fmt .Fprintln (out , " Follow the audit trail datumctl activity" )
118121 fmt .Fprintln (out , " Switch context datumctl ctx" )
119122 }
120123 fmt .Fprintln (out , " Switch account datumctl auth switch" )
121124 fmt .Fprintln (out )
122125
126+ // Installed plugins become `datumctl <command>` verbs, so reflect the user's
127+ // own setup on the landing. Best-effort and local-only — never blocks the
128+ // landing on a missing or unreadable plugin store.
129+ printInstalledPlugins (out )
130+
123131 fmt .Fprintf (out , "Tip: %s\n " , pickTip (time .Now ().UnixNano ()))
124132 fmt .Fprintln (out )
125133
126134 fmt .Fprintln (out , "Run 'datumctl --help' for the full command reference." )
127135}
128136
137+ // printInstalledPlugins renders a "Your plugins" block listing each installed
138+ // plugin as a runnable `datumctl <command>` verb with the catalog it came from.
139+ // It is best-effort and local-only: any error reading the plugin store, or no
140+ // installed plugins, simply prints nothing.
141+ func printInstalledPlugins (out io.Writer ) {
142+ dir , err := pluginstore .PluginsDir ("" )
143+ if err != nil {
144+ return
145+ }
146+ manifest , err := pluginstore .Load (dir )
147+ if err != nil || manifest == nil || len (manifest .Plugins ) == 0 {
148+ return
149+ }
150+
151+ names := make ([]string , 0 , len (manifest .Plugins ))
152+ for name := range manifest .Plugins {
153+ names = append (names , name )
154+ }
155+ sort .Strings (names )
156+
157+ // Pad the command column so the source labels line up.
158+ cmdWidth := 0
159+ for _ , name := range names {
160+ if w := len ("datumctl " + name ); w > cmdWidth {
161+ cmdWidth = w
162+ }
163+ }
164+
165+ for i , name := range names {
166+ label := ""
167+ if i == 0 {
168+ label = "Your plugins"
169+ }
170+ command := "datumctl " + name
171+ fmt .Fprintf (out , " %-22s %-*s (%s)\n " , label , cmdWidth , command , landingPluginSource (manifest .Plugins [name ]))
172+ }
173+ fmt .Fprintln (out )
174+ }
175+
176+ // landingPluginSource returns a short catalog label for an installed plugin,
177+ // mirroring how `plugin list` labels provenance.
178+ func landingPluginSource (entry * pluginstore.InstalledPlugin ) string {
179+ if entry == nil {
180+ return ""
181+ }
182+ if entry .Catalog != "" {
183+ return pluginstore .CanonicalCatalogName (entry .Catalog )
184+ }
185+ // Legacy records: a slash in Source means a direct GitHub install; otherwise
186+ // it came from the curated official catalog.
187+ if strings .Contains (entry .Source , "/" ) {
188+ return "direct"
189+ }
190+ return pluginstore .OfficialCatalogName
191+ }
192+
129193// firstName returns the first whitespace-delimited token of full. Returns ""
130194// for empty/whitespace input.
131195func firstName (full string ) string {
@@ -169,6 +233,10 @@ var landingTips = []string{
169233 // Audit tips
170234 "'datumctl activity' tails the audit trail across your whole control plane." ,
171235 "'datumctl activity --start-time now-1h' scopes the feed to the last hour." ,
236+ // Plugin / marketplace tips
237+ "'datumctl plugin browse' explores plugins across every registered catalog." ,
238+ "'datumctl plugin search <keyword>' finds plugins to install from any catalog." ,
239+ "'datumctl plugin index add <name> <url>' registers a team or community catalog." ,
172240 // Power-user tips
173241 "Set DATUM_PROJECT or DATUM_ORGANIZATION to override context for a single command." ,
174242 "'datumctl describe <resource> <name>' shows status conditions — handy for debugging." ,
0 commit comments