From 69026b12d0bde025a39854696cecbda5ce909e6b Mon Sep 17 00:00:00 2001 From: Amolith Date: Fri, 19 Dec 2025 15:37:35 -0700 Subject: [PATCH] feat(client): add dynamic version and UA option Version is now determined at runtime via debug.ReadBuildInfo() instead of being hardcoded. WithUserAgent option allows consumers to set a custom User-Agent prefix; the library identifier is always appended. Assisted-by: Claude Opus 4.5 via Crush --- lunatask.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/lunatask.go b/lunatask.go index a2d80b9a71cd8dcfa80a42004f16158b25853a74..bbcd2365335625a200eee20e89bcaa09e78369ed 100644 --- a/lunatask.go +++ b/lunatask.go @@ -18,6 +18,8 @@ import ( "fmt" "io" "net/http" + "runtime/debug" + "sync" ) // API error types for typed error handling. @@ -96,14 +98,48 @@ const ( DefaultBaseURL = "https://api.lunatask.app/v1" // statusCloudflareTimeout is Cloudflare's "A Timeout Occurred" status. statusCloudflareTimeout = 524 + // modulePath is the import path used to find this module's version. + modulePath = "git.secluded.site/go-lunatask" ) +var ( + versionOnce sync.Once //nolint:gochecknoglobals + version = "unknown" +) + +// getVersion returns the library version, determined from build info. +func getVersion() string { + versionOnce.Do(func() { + buildInfo, ok := debug.ReadBuildInfo() + if !ok { + return + } + + // When used as a dependency, find our version in the deps list. + for _, dep := range buildInfo.Deps { + if dep.Path == modulePath { + version = dep.Version + + return + } + } + + // When running as main module (e.g., tests), use main module version. + if buildInfo.Main.Path == modulePath && buildInfo.Main.Version != "" && buildInfo.Main.Version != "(devel)" { + version = buildInfo.Main.Version + } + }) + + return version +} + // Client handles communication with the Lunatask API. // A Client is safe for concurrent use. type Client struct { accessToken string baseURL string httpClient *http.Client + userAgent string } // Option configures a Client. @@ -123,6 +159,14 @@ func WithBaseURL(url string) Option { } } +// WithUserAgent sets a custom User-Agent prefix. +// The library identifier (go-lunatask/version) is always appended. +func WithUserAgent(ua string) Option { + return func(c *Client) { + c.userAgent = ua + } +} + // NewClient creates a new Lunatask API client. // Generate an access token in the Lunatask desktop app under Settings → Access tokens. func NewClient(accessToken string, opts ...Option) *Client { @@ -130,6 +174,7 @@ func NewClient(accessToken string, opts ...Option) *Client { accessToken: accessToken, baseURL: DefaultBaseURL, httpClient: &http.Client{}, //nolint:exhaustruct + userAgent: "", } for _, opt := range opts { opt(client) @@ -156,6 +201,13 @@ func (c *Client) Ping(ctx context.Context) (*PingResponse, error) { func (c *Client) doRequest(req *http.Request) ([]byte, int, error) { req.Header.Set("Authorization", "bearer "+c.accessToken) + ua := "go-lunatask/" + getVersion() + if c.userAgent != "" { + ua = c.userAgent + " " + ua + } + + req.Header.Set("User-Agent", ua) + resp, err := c.httpClient.Do(req) if err != nil { return nil, 0, fmt.Errorf("failed to send HTTP request: %w", err)