Files
tapir/cmd/tapir/show.go
T
mathiasandClaude Sonnet 4.6 38f222c931
CI / Lint / Test / Vet (push) Successful in 11s
CI / Build & Import (push) Successful in 12s
chore: rename Go module path gitea.d-ma.be → git.d-ma.be
Infra ADR-0004 renamed the Gitea host. Bulk replace across go.mod and
all .go import paths. Build and tests pass unchanged.

Closes #20

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dt6aHEDWRjkK14Voi6HnGh
2026-07-02 14:37:33 +02:00

87 lines
2.2 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"io"
"os"
"strings"
"git.d-ma.be/mathias/tapir/internal/adapters/store"
)
// runShow prints the full summary for one video: text, highlights, takeaways,
// and provenance. Read-only. Usage: tapir show <video-id>.
func runShow(ctx context.Context, args []string) error {
if len(args) != 1 || strings.TrimSpace(args[0]) == "" {
return errors.New("usage: tapir show <video-id>")
}
videoID := args[0]
s, userID, closeFn, err := openStoreFromEnv(ctx)
if err != nil {
return err
}
defer closeFn()
row, err := s.GetSummaryByVideo(ctx, userID, videoID)
if err != nil {
if errors.Is(err, store.ErrNotFound) {
return fmt.Errorf("no summary for video %q", videoID)
}
return err
}
return formatShow(os.Stdout, *row)
}
// formatShow renders a single summary for terminal reading. Pure: no DB, no env,
// so it is unit-testable. Empty sections are omitted rather than printed blank.
func formatShow(w io.Writer, r store.SummaryRow) error {
var b strings.Builder
fmt.Fprintf(&b, "%s\n", displayTitle(r))
if d := listDate(r); d != "-" {
fmt.Fprintf(&b, "Date: %s\n", d)
}
if strings.TrimSpace(r.Channel) != "" {
fmt.Fprintf(&b, "Channel: %s\n", r.Channel)
}
if strings.TrimSpace(r.URL) != "" {
fmt.Fprintf(&b, "URL: %s\n", r.URL)
}
fmt.Fprintf(&b, "Video ID: %s\n", r.VideoID)
fmt.Fprintf(&b, "AI: %s\n", aiProvenance(r))
fmt.Fprintf(&b, "\n%s\n", strings.TrimSpace(r.Summary))
writeList(&b, "Highlights", r.Highlights)
writeList(&b, "Takeaways", r.Takeaways)
_, err := io.WriteString(w, b.String())
return err
}
// aiProvenance summarises which model produced the summary and whether the local
// stack fell back to a cloud provider — the Stage-0 "is local good enough?" signal.
func aiProvenance(r store.SummaryRow) string {
parts := orDash(r.AIProvider)
if strings.TrimSpace(r.AIModel) != "" {
parts += "/" + r.AIModel
}
if r.FallbackUsed {
parts += " (fallback)"
}
return parts
}
func writeList(b *strings.Builder, heading string, items []string) {
if len(items) == 0 {
return
}
fmt.Fprintf(b, "\n%s:\n", heading)
for _, it := range items {
fmt.Fprintf(b, " - %s\n", it)
}
}