commit 4c084e8a399a8521950a8edaf9b58c4f7eca67d2
parent a7b927ecb9678cc86c1354e35b535beef24ba3d1
Author: Damian Myrda <damian@prime8.dev>
Date: Wed, 21 Oct 2026 02:50:31 +0000
feat: curl prime8.dev prints the fetch block as ansi text
Diffstat:
4 files changed, 147 insertions(+), 31 deletions(-)
diff --git a/cmd/server/handlers/curl.go b/cmd/server/handlers/curl.go
@@ -0,0 +1,106 @@
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+ "unicode/utf8"
+
+ "git.prime8.dev/moncheeta/prime8dev/internal/config"
+ "git.prime8.dev/moncheeta/prime8dev/internal/utils"
+)
+
+const (
+ curlLogoWidth = 32
+ ansiReset = "\x1b[0m"
+ ansiDim = "\x1b[38;5;242m"
+ ansiAccent = "\x1b[38;5;179m"
+ ansiGreen = "\x1b[38;5;71m"
+ ansiBlue = "\x1b[38;5;110m"
+)
+
+var curlLogo = sync.OnceValue(func() string { return utils.LogoASCII(curlLogoWidth, false) })
+
+// isTerminalClient reports whether the request comes from a CLI http client.
+func isTerminalClient(r *http.Request) bool {
+ ua := strings.ToLower(r.UserAgent())
+ for _, p := range []string{"curl/", "wget/", "httpie/", "xh/"} {
+ if strings.HasPrefix(ua, p) {
+ return true
+ }
+ }
+ return false
+}
+
+func (h *Handler) serveTerminalFetch(w http.ResponseWriter) {
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+ w.Header().Set("Vary", "User-Agent")
+ fmt.Fprint(w, terminalFetch(config.GetAbout(), config.CurrentJob(config.GetExperience())))
+}
+
+func terminalFetch(about config.About, job *config.Experience) string {
+ role := about.Profession
+ if job != nil {
+ role = job.Role + " @ " + job.Company
+ }
+ title := about.Handle + "@prime8"
+
+ row := func(key, value string) string { return ansiAccent + key + ansiReset + ": " + value }
+ lines := []string{
+ ansiGreen + about.Handle + ansiReset + ansiDim + "@" + ansiReset + ansiAccent + "prime8" + ansiReset,
+ ansiDim + strings.Repeat("-", len(title)) + ansiReset,
+ row("name", about.Name),
+ row("role", role),
+ row("location", about.Location),
+ row("uptime", utils.Uptime(config.Birthday, time.Now())),
+ }
+ for _, kv := range about.Setup {
+ lines = append(lines, row(kv.Key, kv.Value))
+ }
+ langs := make([]string, len(about.Languages))
+ for i, l := range about.Languages {
+ langs[i] = hexFG(utils.TagColor(l)) + l + ansiReset
+ }
+ lines = append(lines,
+ row("langs", strings.Join(langs, ", ")),
+ row("email", ansiBlue+"damian@prime8.dev"+ansiReset),
+ row("web", ansiBlue+"https://prime8.dev"+ansiReset),
+ "",
+ ansiDim+"the full thing: "+ansiReset+ansiGreen+"ssh prime8.dev"+ansiReset,
+ )
+
+ logo := strings.Split(curlLogo(), "\n")
+ if len(logo) == 1 && logo[0] == "" {
+ return "\n" + strings.Join(lines, "\n") + "\n\n"
+ }
+
+ var b strings.Builder
+ b.WriteString("\n")
+ for i := 0; i < max(len(logo), len(lines)); i++ {
+ l := ""
+ if i < len(logo) {
+ l = logo[i]
+ }
+ b.WriteString(l + strings.Repeat(" ", max(0, curlLogoWidth-utf8.RuneCountInString(l))) + " ")
+ if i < len(lines) {
+ b.WriteString(lines[i])
+ }
+ b.WriteString("\n")
+ }
+ b.WriteString("\n")
+ return b.String()
+}
+
+func hexFG(hex string) string {
+ if len(hex) != 7 || hex[0] != '#' {
+ return ""
+ }
+ v, err := strconv.ParseUint(hex[1:], 16, 32)
+ if err != nil {
+ return ""
+ }
+ return fmt.Sprintf("\x1b[38;2;%d;%d;%dm", v>>16, v>>8&0xff, v&0xff)
+}
diff --git a/cmd/server/handlers/home.go b/cmd/server/handlers/home.go
@@ -9,6 +9,10 @@ import (
)
func (h *Handler) HomeHandler(w http.ResponseWriter, r *http.Request) {
+ if isTerminalClient(r) {
+ h.serveTerminalFetch(w)
+ return
+ }
about := config.GetAbout()
experience := config.GetExperience()
data := web.BaseData("About", "about", about)
diff --git a/internal/tui/about_ascii.go b/internal/tui/about_ascii.go
@@ -1,40 +1,11 @@
package tui
-import (
- "path/filepath"
- "strings"
- "sync"
-
- aic "github.com/TheZoraiz/ascii-image-converter/aic_package"
-)
-
-var aboutASCIIConvertMu sync.Mutex
+import "git.prime8.dev/moncheeta/prime8dev/internal/utils"
func (m portfolioModel) aboutASCIIFor(width int) string {
return m.aboutASCIICache[width]
}
func loadAboutASCII(width int) string {
- flags := aic.DefaultFlags()
- flags.Width = width
- flags.Colored = true
- flags.Braille = false
- flags.Dither = true
- flags.Threshold = 160
-
- aboutASCIIConvertMu.Lock()
- defer aboutASCIIConvertMu.Unlock()
-
- path := filepath.Join("static", "me.jpg")
- ascii, err := aic.Convert(path, flags)
- if err != nil {
- // aic refuses color when the server's terminal env lacks COLORTERM;
- // fall back to plain ascii so the logo always renders.
- flags.Colored = false
- if ascii, err = aic.Convert(path, flags); err != nil {
- return ""
- }
- }
-
- return strings.TrimRight(ascii, "\n")
+ return utils.LogoASCII(width, true)
}
diff --git a/internal/utils/logo.go b/internal/utils/logo.go
@@ -0,0 +1,35 @@
+package utils
+
+import (
+ "path/filepath"
+ "strings"
+ "sync"
+
+ aic "github.com/TheZoraiz/ascii-image-converter/aic_package"
+)
+
+var logoMu sync.Mutex
+
+// LogoASCII renders static/me.jpg as ascii art. aic refuses color when the
+// server's env lacks COLORTERM, so it falls back to plain ascii.
+func LogoASCII(width int, colored bool) string {
+ flags := aic.DefaultFlags()
+ flags.Width = width
+ flags.Colored = colored
+ flags.Dither = true
+ flags.Threshold = 160
+
+ logoMu.Lock()
+ defer logoMu.Unlock()
+
+ path := filepath.Join("static", "me.jpg")
+ ascii, err := aic.Convert(path, flags)
+ if err != nil && colored {
+ flags.Colored = false
+ ascii, err = aic.Convert(path, flags)
+ }
+ if err != nil {
+ return ""
+ }
+ return strings.TrimRight(ascii, "\n")
+}