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
87 lines
2.2 KiB
Go
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)
|
|
}
|
|
}
|