[object Object]

← back to Cli Printing Press

fix(generator): auth format, module path, and example values

0063ee7611abe10e8c219f1de00b724a53e86565 · 2026-03-26 12:41:03 -0700 · Matt Van Horn

- Auth: client.go.tmpl now respects Auth.Format (Bot/Bearer/Basic),
  Auth.Header (custom header names), and Auth.In (header/query)
- Config: AuthHeader() applies format strings (e.g., "Bot {bot_token}")
- Module path: derive from git config github.user/user.name, sanitize
  for Go module paths (lowercase, no spaces/special chars)
- Examples: exampleValue() produces UUIDs for IDs, emails for email
  fields, dates for date fields (not "abc123" everywhere)
- Templates: replace hardcoded "USER" with {{.Owner}} in sync/search

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 0063ee7611abe10e8c219f1de00b724a53e86565
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date:   Thu Mar 26 12:41:03 2026 -0700

    fix(generator): auth format, module path, and example values
    
    - Auth: client.go.tmpl now respects Auth.Format (Bot/Bearer/Basic),
      Auth.Header (custom header names), and Auth.In (header/query)
    - Config: AuthHeader() applies format strings (e.g., "Bot {bot_token}")
    - Module path: derive from git config github.user/user.name, sanitize
      for Go module paths (lowercase, no spaces/special chars)
    - Examples: exampleValue() produces UUIDs for IDs, emails for email
      fields, dates for date fields (not "abc123" everywhere)
    - Templates: replace hardcoded "USER" with {{.Owner}} in sync/search
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 internal/generator/generator.go                | 79 +++++++++++++++++---------
 internal/generator/templates/analytics.go.tmpl |  2 +-
 internal/generator/templates/client.go.tmpl    |  9 ++-
 internal/generator/templates/config.go.tmpl    | 50 +++++++++++++---
 internal/generator/templates/search.go.tmpl    |  2 +-
 internal/generator/templates/sync.go.tmpl      |  2 +-
 6 files changed, 106 insertions(+), 38 deletions(-)

diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index d44c094b..83b46793 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -4,6 +4,7 @@ import (
 	"embed"
 	"fmt"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"strconv"
 	"strings"
@@ -27,10 +28,24 @@ type Generator struct {
 }
 
 func New(s *spec.APISpec, outputDir string) *Generator {
-	// Default Owner to "USER" for backward compatibility
 	if s.Owner == "" {
-		s.Owner = "USER"
+		if out, err := exec.Command("git", "config", "github.user").Output(); err == nil && len(out) > 0 {
+			s.Owner = strings.TrimSpace(string(out))
+		} else if out, err := exec.Command("git", "config", "user.name").Output(); err == nil && len(out) > 0 {
+			s.Owner = strings.TrimSpace(string(out))
+		} else {
+			s.Owner = "USER"
+		}
 	}
+	// Sanitize owner for Go module path: lowercase, no spaces/special chars
+	s.Owner = strings.ToLower(s.Owner)
+	s.Owner = strings.ReplaceAll(s.Owner, " ", "-")
+	s.Owner = strings.Map(func(r rune) rune {
+		if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_' {
+			return r
+		}
+		return -1
+	}, s.Owner)
 	g := &Generator{Spec: s, OutputDir: outputDir}
 	g.funcs = template.FuncMap{
 		"title":              strings.Title,
@@ -592,32 +607,42 @@ func oneline(s string) string {
 	return s
 }
 
-func exampleValue(paramName, paramType string) string {
-	name := strings.ToLower(paramName)
-	switch {
-	case strings.HasSuffix(name, "_id") || strings.HasSuffix(name, "id") || name == "id":
-		return "abc123"
-	case strings.Contains(name, "email"):
+func exampleValue(p spec.Param) string {
+	nameLower := strings.ToLower(p.Name)
+
+	if strings.HasSuffix(nameLower, "_id") || nameLower == "id" {
+		return "550e8400-e29b-41d4-a716-446655440000"
+	}
+	if strings.Contains(nameLower, "email") {
 		return "user@example.com"
-	case strings.Contains(name, "name"):
-		return "my-resource"
-	case strings.Contains(name, "date") || strings.HasSuffix(name, "_at"):
-		return "2026-01-01"
-	case strings.Contains(name, "url") || strings.Contains(name, "link"):
-		return "https://example.com"
-	case strings.Contains(name, "status"):
-		return "active"
-	case strings.Contains(name, "limit") || strings.Contains(name, "count"):
-		return "25"
-	case strings.Contains(name, "page"):
-		return "1"
-	case paramType == "integer" || paramType == "int":
+	}
+	if strings.Contains(nameLower, "url") || strings.Contains(nameLower, "link") {
+		return "https://example.com/resource"
+	}
+	if strings.Contains(nameLower, "name") || strings.Contains(nameLower, "title") {
+		return "example-resource"
+	}
+	if strings.Contains(nameLower, "date") || p.Format == "date" {
+		return "2026-01-15"
+	}
+	if strings.Contains(nameLower, "time") || p.Format == "date-time" {
+		return "2026-01-15T09:00:00Z"
+	}
+	if strings.Contains(nameLower, "token") || strings.Contains(nameLower, "key") {
+		return "your-token-here"
+	}
+	if strings.Contains(nameLower, "limit") || strings.Contains(nameLower, "count") || strings.Contains(nameLower, "size") {
+		if p.Type == "integer" || p.Type == "int" {
+			return "50"
+		}
+	}
+	if p.Type == "boolean" || p.Type == "bool" {
+		return "true"
+	}
+	if p.Type == "integer" || p.Type == "int" || p.Type == "number" || p.Type == "float" {
 		return "42"
-	case paramType == "boolean" || paramType == "bool":
-		return ""
-	default:
-		return "value"
 	}
+	return "example-value"
 }
 
 func (g *Generator) exampleLine(commandPath, endpointName string, endpoint spec.Endpoint) string {
@@ -629,7 +654,7 @@ func (g *Generator) exampleLine(commandPath, endpointName string, endpoint spec.
 	// Add positional arg placeholders with realistic values
 	for _, p := range endpoint.Params {
 		if p.Positional {
-			val := exampleValue(p.Name, p.Type)
+			val := exampleValue(p)
 			if val == "" {
 				val = "<" + p.Name + ">"
 			}
@@ -642,7 +667,7 @@ func (g *Generator) exampleLine(commandPath, endpointName string, endpoint spec.
 	case "POST", "PUT", "PATCH":
 		for _, p := range endpoint.Body {
 			if p.Required && p.Type == "string" {
-				val := exampleValue(p.Name, p.Type)
+				val := exampleValue(p)
 				if val == "" {
 					val = "value"
 				}
diff --git a/internal/generator/templates/analytics.go.tmpl b/internal/generator/templates/analytics.go.tmpl
index 572a1c44..b8b21c74 100644
--- a/internal/generator/templates/analytics.go.tmpl
+++ b/internal/generator/templates/analytics.go.tmpl
@@ -10,7 +10,7 @@ import (
 	"path/filepath"
 	"sort"
 
-	"github.com/USER/{{.Name}}-cli/internal/store"
+	"github.com/{{.Owner}}/{{.Name}}-cli/internal/store"
 	"github.com/spf13/cobra"
 )
 
diff --git a/internal/generator/templates/client.go.tmpl b/internal/generator/templates/client.go.tmpl
index 249305b5..daf6cea5 100644
--- a/internal/generator/templates/client.go.tmpl
+++ b/internal/generator/templates/client.go.tmpl
@@ -147,7 +147,14 @@ func (c *Client) do(method, path string, params map[string]string, body any) (js
 			return nil, err
 		}
 		if authHeader != "" {
-			req.Header.Set("Authorization", authHeader)
+{{- if and .Auth .Auth.In (eq .Auth.In "query")}}
+			// API key goes in query parameter
+			q := req.URL.Query()
+			q.Set("{{if .Auth.Header}}{{.Auth.Header}}{{else}}api_key{{end}}", authHeader)
+			req.URL.RawQuery = q.Encode()
+{{- else}}
+			req.Header.Set("{{if .Auth.Header}}{{.Auth.Header}}{{else}}Authorization{{end}}", authHeader)
+{{- end}}
 		}
 		if bodyBytes != nil {
 			req.Header.Set("Content-Type", "application/json")
diff --git a/internal/generator/templates/config.go.tmpl b/internal/generator/templates/config.go.tmpl
index 6f81ae4d..755e58dc 100644
--- a/internal/generator/templates/config.go.tmpl
+++ b/internal/generator/templates/config.go.tmpl
@@ -74,27 +74,50 @@ func (c *Config) AuthHeader() string {
 		return c.AuthHeaderVal
 	}
 {{- if eq .Auth.Type "api_key"}}
+	{{- if gt (len .Auth.EnvVars) 0}}
+	token := c.{{envVarField (index .Auth.EnvVars 0)}}
+	if token == "" {
+		return ""
+	}
+	{{- end}}
 {{- range .Auth.EnvVars}}
 	if c.{{envVarField .}} == "" {
 		return ""
 	}
 {{- end}}
-	format := "{{.Auth.Format}}"
-{{- range .Auth.EnvVars}}
-	format = strings.ReplaceAll(format, "{ {{- envVarPlaceholder . -}} }", c.{{envVarField .}})
-{{- end}}
-	if strings.Contains(format, "{") {
-		return "" // not all vars resolved
+	{{- if .Auth.Format}}
+	replacements := map[string]string{
+	{{- range .Auth.EnvVars}}
+		"{{envVarPlaceholder .}}": c.{{envVarField .}},
+		"{{.}}": c.{{envVarField .}},
+	{{- end}}
 	}
-	return format
+	return applyAuthFormat("{{.Auth.Format}}", replacements)
+	{{- else if gt (len .Auth.EnvVars) 0}}
+	return token
+	{{- else}}
+	return ""
+	{{- end}}
 {{- else if eq .Auth.Type "bearer_token"}}
 	if c.AccessToken != "" {
 		c.AuthSource = "oauth2"
+		{{- if .Auth.Format}}
+		return applyAuthFormat("{{.Auth.Format}}", map[string]string{"access_token": c.AccessToken, "token": c.AccessToken})
+		{{- else}}
 		return "Bearer " + c.AccessToken
+		{{- end}}
 	}
 	{{- if gt (len .Auth.EnvVars) 0}}
 	if c.{{envVarField (index .Auth.EnvVars 0)}} != "" {
+		{{- if .Auth.Format}}
+		return applyAuthFormat("{{.Auth.Format}}", map[string]string{
+			"{{envVarPlaceholder (index .Auth.EnvVars 0)}}": c.{{envVarField (index .Auth.EnvVars 0)}},
+			"{{index .Auth.EnvVars 0}}": c.{{envVarField (index .Auth.EnvVars 0)}},
+			"token": c.{{envVarField (index .Auth.EnvVars 0)}},
+		})
+		{{- else}}
 		return "Bearer " + c.{{envVarField (index .Auth.EnvVars 0)}}
+		{{- end}}
 	}
 	{{- end}}
 	return ""
@@ -103,6 +126,19 @@ func (c *Config) AuthHeader() string {
 {{- end}}
 }
 
+func applyAuthFormat(format string, replacements map[string]string) string {
+	if format == "" {
+		return ""
+	}
+	for key, value := range replacements {
+		format = strings.ReplaceAll(format, "{"+key+"}", value)
+	}
+	if strings.Contains(format, "{") {
+		return ""
+	}
+	return format
+}
+
 func (c *Config) SaveTokens(clientID, clientSecret, accessToken, refreshToken string, expiry time.Time) error {
 	c.ClientID = clientID
 	c.ClientSecret = clientSecret
diff --git a/internal/generator/templates/search.go.tmpl b/internal/generator/templates/search.go.tmpl
index 2726b822..b88bac93 100644
--- a/internal/generator/templates/search.go.tmpl
+++ b/internal/generator/templates/search.go.tmpl
@@ -9,7 +9,7 @@ import (
 	"os"
 	"path/filepath"
 
-	"github.com/USER/{{.Name}}-cli/internal/store"
+	"github.com/{{.Owner}}/{{.Name}}-cli/internal/store"
 	"github.com/spf13/cobra"
 )
 
diff --git a/internal/generator/templates/sync.go.tmpl b/internal/generator/templates/sync.go.tmpl
index 0749adce..c1480da9 100644
--- a/internal/generator/templates/sync.go.tmpl
+++ b/internal/generator/templates/sync.go.tmpl
@@ -9,7 +9,7 @@ import (
 	"os"
 	"path/filepath"
 
-	"github.com/USER/{{.Name}}-cli/internal/store"
+	"github.com/{{.Owner}}/{{.Name}}-cli/internal/store"
 	"github.com/spf13/cobra"
 )
 

← eb59816b feat(generator): wire BuildSchema to store/sync/search templ  ·  back to Cli Printing Press  ·  feat(skill): add Phase 4.6 hallucination audit and anti-gami 7bcacea3 →