The product, module path (github.com/yetone/magpie), binaries, env vars (MAGPIE_ADDR, MAGPIE_DEV_UI, MAGPIE_THEME, MAGPIE_DEBUG), config and cache folders (~/.config/magpie, ~/.cache/magpie), bundle id (com.yetone.magpie), gateway provider id and token, and every UI string now say magpie. On first run magpie copies ~/.config/dial and ~/.cache/dial to the new folders when they don't exist yet (copy, not move, so a still-running dial keeps working; the old folders can be deleted by hand later). The logo is a small round magpie in profile: one path for the header and the tray, and the app tile adds the white belly and the blue-green tail. The header mark is drawn in ink rather than the accent colour, which fought the text in light mode.
116 lines
3.2 KiB
Go
116 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/yetone/magpie/internal/agent"
|
|
stats "github.com/yetone/magpie/internal/usage"
|
|
)
|
|
|
|
// usageCmd: `magpie usage [today|7d|30d|all]` — tokens and cost per agent and model
|
|
func usageCmd(args []string) error {
|
|
period := stats.Month
|
|
if len(args) > 1 {
|
|
switch strings.ToLower(args[1]) {
|
|
case "today", "day":
|
|
period = stats.Today
|
|
case "7d", "week":
|
|
period = stats.Week
|
|
case "30d", "month":
|
|
period = stats.Month
|
|
case "all":
|
|
period = stats.All
|
|
default:
|
|
return fmt.Errorf("usage: magpie usage [today|7d|30d|all]")
|
|
}
|
|
}
|
|
s := stats.Summarize(period)
|
|
title := map[stats.Period]string{stats.Today: "today", stats.Week: "last 7 days", stats.Month: "last 30 days", stats.All: "all time"}[s.Period]
|
|
if s.Calls == 0 {
|
|
fmt.Println(muted.Render("no calls "+title+" ·"), "route an agent through magpie and its usage shows up here")
|
|
fmt.Println(faint.Render(" " + stats.Path()))
|
|
return nil
|
|
}
|
|
fmt.Println(bold.Render(fmtTokens(s.Tokens())+" tokens"), muted.Render(title+" ·"), plural(s.Calls, "call"), muted.Render("·"), cost(s.Totals))
|
|
fmt.Println(muted.Render(" in "+fmtTokens(s.Input)+" out "+fmtTokens(s.Output)+" cache read "+fmtTokens(s.CacheRead)+" cache write "+fmtTokens(s.CacheWrite)+" reasoning "+fmtTokens(s.Reasoning)),
|
|
func() string {
|
|
if s.Errors > 0 {
|
|
return muted.Render(" · ") + plural(s.Errors, "error")
|
|
}
|
|
return ""
|
|
}())
|
|
|
|
names := map[string]string{}
|
|
for _, a := range agent.All() {
|
|
names[a.ID] = a.Name
|
|
}
|
|
table := func(head string, gs []stats.Group, name func(stats.Group) string) {
|
|
fmt.Println()
|
|
fmt.Println(faint.Render(" " + head))
|
|
w := 0
|
|
for _, g := range gs {
|
|
w = max(w, len(name(g)))
|
|
}
|
|
for _, g := range gs {
|
|
share := ""
|
|
if s.Tokens() > 0 {
|
|
share = fmt.Sprintf("%3.0f%%", 100*float64(g.Tokens())/float64(s.Tokens()))
|
|
}
|
|
fmt.Println(" "+pad(name(g), w), muted.Render(share), pad(fmtTokens(g.Tokens()), 7), faint.Render(pad(plural(g.Calls, "call"), 10)), cost(g.Totals))
|
|
}
|
|
}
|
|
table("agents", s.Agents, func(g stats.Group) string {
|
|
if n := names[g.ID]; n != "" {
|
|
return n
|
|
}
|
|
return g.ID
|
|
})
|
|
table("models", s.Models, func(g stats.Group) string { return g.ID })
|
|
fmt.Println(faint.Render(" " + stats.Path()))
|
|
return nil
|
|
}
|
|
|
|
func plural(n int, unit string) string {
|
|
if n == 1 {
|
|
return "1 " + unit
|
|
}
|
|
return fmt.Sprintf("%d %ss", n, unit)
|
|
}
|
|
|
|
func fmtTokens(n int) string {
|
|
switch {
|
|
case n >= 1_000_000_000:
|
|
return fmt.Sprintf("%.2fB", float64(n)/1e9)
|
|
case n >= 10_000_000:
|
|
return fmt.Sprintf("%.0fM", float64(n)/1e6)
|
|
case n >= 1_000_000:
|
|
return fmt.Sprintf("%.1fM", float64(n)/1e6)
|
|
case n >= 100_000:
|
|
return fmt.Sprintf("%.0fK", float64(n)/1e3)
|
|
case n >= 1_000:
|
|
return fmt.Sprintf("%.1fK", float64(n)/1e3)
|
|
}
|
|
return fmt.Sprint(n)
|
|
}
|
|
|
|
// cost renders list-price cost, saying when some calls could not be priced.
|
|
func cost(t stats.Totals) string {
|
|
if t.Cost == 0 && t.Unpriced > 0 {
|
|
return faint.Render("no price")
|
|
}
|
|
var s string
|
|
switch {
|
|
case t.Cost >= 100:
|
|
s = fmt.Sprintf("$%.0f", t.Cost)
|
|
case t.Cost >= 1:
|
|
s = fmt.Sprintf("$%.2f", t.Cost)
|
|
default:
|
|
s = fmt.Sprintf("$%.3f", t.Cost)
|
|
}
|
|
if t.Unpriced > 0 {
|
|
s += muted.Render("+")
|
|
}
|
|
return green.Render("≈" + s)
|
|
}
|