// Copyright (c) 2026 Probo Inc . // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. package commonthirdparty import ( "context" "encoding/json" "fmt" "io" "sort" "strings" "time" "github.com/charmbracelet/lipgloss" "github.com/spf13/cobra" "go.gearno.de/kit/pg" clicmdutil "go.probo.inc/probo/pkg/cmd/cmdutil" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/proboctl/cmdutil" ) // enrichmentMetadataView mirrors the subset of the enrichment payload // (written by the common-third-party enrichment worker) that show // renders. It is decoded locally to avoid a dependency on the thirdparty // package. type enrichmentMetadataView struct { Model string `json:"model"` AttemptedAt time.Time `json:"attempted_at"` Status string `json:"status"` Error string `json:"error"` Fields map[string]enrichmentFieldMetaView `json:"fields"` Domains []enrichmentDomainMetaView `json:"domains"` } type enrichmentFieldMetaView struct { Confidence float64 `json:"confidence"` SourceURL string `json:"source_url"` Status string `json:"status"` Source string `json:"source"` } type enrichmentDomainMetaView struct { Domain string `json:"domain"` Confidence float64 `json:"confidence"` } func newCmdShow(f *cmdutil.Factory) *cobra.Command { cmd := &cobra.Command{ Use: "show ", Short: "Show a single common third party with its domains and linked pattern count", Args: cobra.ExactArgs(1), } output := clicmdutil.AddOutputFlag(cmd) cmd.RunE = func(cmd *cobra.Command, args []string) error { if err := clicmdutil.ValidateOutputFlag(output); err != nil { return err } pgClient, err := f.PgClient() if err != nil { return err } var ( party coredata.CommonThirdParty domains coredata.CommonThirdPartyDomains patternCount int ) if err := pgClient.WithConn( cmd.Context(), func(ctx context.Context, conn pg.Querier) error { party, err = resolveCommonThirdParty(ctx, conn, args[0]) if err != nil { return err } if err := domains.LoadByCommonThirdPartyID(ctx, conn, party.ID); err != nil { return fmt.Errorf("cannot load domains: %w", err) } var patterns coredata.CommonTrackerPatterns if err := patterns.LoadByCommonThirdPartyID(ctx, conn, party.ID); err != nil { return fmt.Errorf("cannot load linked patterns: %w", err) } patternCount = len(patterns) return nil }, ); err != nil { return err } if *output == clicmdutil.OutputJSON { return clicmdutil.PrintJSON(f.IOStreams.Out, map[string]any{ "thirdParty": party, "domains": domains, "linkedPatternCount": patternCount, }) } out := f.IOStreams.Out label := lipgloss.NewStyle().Foreground(lipgloss.Color("242")).Width(20) row := func(name, value string) { _, _ = fmt.Fprintf(out, "%s%s\n", label.Render(name), value) } row("ID:", party.ID.String()) row("Name:", party.Name) row("Slug:", party.Slug) row("Category:", string(party.Category)) if party.WebsiteURL != nil { row("Website:", *party.WebsiteURL) } domainNames := make([]string, 0, len(domains)) for _, d := range domains { domainNames = append(domainNames, d.Domain) } if len(domainNames) > 0 { row("Domains:", strings.Join(domainNames, ", ")) } else { row("Domains:", "(none)") } row("Linked patterns:", fmt.Sprintf("%d", patternCount)) row("Created:", party.CreatedAt.Format("2006-01-02 15:04:05")) row("Updated:", party.UpdatedAt.Format("2006-01-02 15:04:05")) row("Enrichment state:", enrichmentState(&party)) row("Enrichment attempts:", fmt.Sprintf("%d", party.EnrichmentAttempts)) if party.LastEnrichmentAttemptAt != nil { row("Last attempt:", party.LastEnrichmentAttemptAt.Format("2006-01-02 15:04:05")) } if party.EnrichmentRequestedAt != nil { row("Queued at:", party.EnrichmentRequestedAt.Format("2006-01-02 15:04:05")) } printEnrichmentDetails(out, label, party) return nil } return cmd } // printEnrichmentDetails renders the run-level status and per-field // provenance recorded in the enrichment payload, when present. func printEnrichmentDetails(out io.Writer, label lipgloss.Style, party coredata.CommonThirdParty) { if len(party.Enrichment) == 0 { return } var meta enrichmentMetadataView if err := json.Unmarshal(party.Enrichment, &meta); err != nil { return } row := func(name, value string) { _, _ = fmt.Fprintf(out, "%s%s\n", label.Render(name), value) } if meta.Status != "" { row("Last run status:", meta.Status) } if !meta.AttemptedAt.IsZero() { row("Last run recorded:", meta.AttemptedAt.Format("2006-01-02 15:04:05")) } if meta.Model != "" { row("Enrichment model:", meta.Model) } if meta.Error != "" { row("Last error:", meta.Error) } if len(meta.Fields) > 0 { names := make([]string, 0, len(meta.Fields)) for name := range meta.Fields { names = append(names, name) } sort.Strings(names) _, _ = fmt.Fprintln(out) table := clicmdutil.NewTable("FIELD", "STATUS", "SOURCE", "CONF") for _, name := range names { fm := meta.Fields[name] table.Row(name, fm.Status, fm.Source, fmt.Sprintf("%.2f", fm.Confidence)) } _, _ = fmt.Fprintln(out, table.Render()) } if len(meta.Domains) > 0 { _, _ = fmt.Fprintln(out) table := clicmdutil.NewTable("DISCOVERED DOMAIN", "CONF") for _, d := range meta.Domains { table.Row(d.Domain, fmt.Sprintf("%.2f", d.Confidence)) } _, _ = fmt.Fprintln(out, table.Render()) } }