diff --git a/assets/og-image.png b/assets/og-image.png
new file mode 100644
index 0000000..3e43318
Binary files /dev/null and b/assets/og-image.png differ
diff --git a/assets/og-image.svg b/assets/og-image.svg
new file mode 100644
index 0000000..4914806
--- /dev/null
+++ b/assets/og-image.svg
@@ -0,0 +1,54 @@
+
diff --git a/cmd/wk/main.go b/cmd/wk/main.go
index dad63c9..734675d 100644
--- a/cmd/wk/main.go
+++ b/cmd/wk/main.go
@@ -6,9 +6,16 @@ import (
"os/signal"
"github.com/workato-devs/wk/internal/commands"
+ wkversion "github.com/workato-devs/wk/internal/version"
)
-// Set by goreleaser ldflags.
+// These default to "dev"/"none"/"unknown" and are overwritten at build time
+// by the Go linker's -X flags. Two build paths set them:
+// - Makefile: -X main.version=$(VERSION) ... (VERSION from `git describe`)
+// - .goreleaser.yaml: -X main.version={{.Version}} ... (release builds in CI)
+// The variable names must stay version/commit/date so those -X flags keep
+// matching (the linker matches by exact symbol name, e.g. main.version).
+// A plain `go run`/`go build` with no -X flags leaves the defaults in place.
var (
version = "dev"
commit = "none"
@@ -19,6 +26,8 @@ func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
- commands.SetVersionInfo(version, commit, date)
+ // Record build-time version info so every package (CLI version command,
+ // API client User-Agent, MCP client) reads from a single source.
+ wkversion.Set(version, commit, date)
os.Exit(commands.Execute(ctx))
}
diff --git a/internal/api/http_client.go b/internal/api/http_client.go
index 2beae8b..ac75980 100644
--- a/internal/api/http_client.go
+++ b/internal/api/http_client.go
@@ -9,10 +9,9 @@ import (
"net/http"
"os"
"time"
-)
-// userAgent is sent on every outbound Workato API request.
-const userAgent = "wk/dev"
+ "github.com/workato-devs/wk/internal/version"
+)
// HTTPClient implements the Client interface using HTTP.
type HTTPClient struct {
@@ -175,7 +174,7 @@ func (c *HTTPClient) do(ctx context.Context, method, path string, body, result a
}
req.Header.Set("Authorization", "Bearer "+c.token)
- req.Header.Set("User-Agent", userAgent)
+ req.Header.Set("User-Agent", version.UserAgent())
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
@@ -237,7 +236,7 @@ func (c *HTTPClient) doRaw(ctx context.Context, method, path string) ([]byte, er
}
req.Header.Set("Authorization", "Bearer "+c.token)
- req.Header.Set("User-Agent", userAgent)
+ req.Header.Set("User-Agent", version.UserAgent())
if c.verbose {
fmt.Fprintf(os.Stderr, "[debug] %s %s%s\n", method, c.baseURL, path)
diff --git a/internal/api/packages.go b/internal/api/packages.go
index e222279..c7c24a5 100644
--- a/internal/api/packages.go
+++ b/internal/api/packages.go
@@ -9,6 +9,8 @@ import (
"net/http"
"os"
"time"
+
+ "github.com/workato-devs/wk/internal/version"
)
type packageService struct {
@@ -63,7 +65,7 @@ func (s *packageService) Import(ctx context.Context, folderID int, data []byte,
}
req.Header.Set("Authorization", "Bearer "+s.client.token)
- req.Header.Set("User-Agent", userAgent)
+ req.Header.Set("User-Agent", version.UserAgent())
req.Header.Set("Content-Type", "application/octet-stream")
if s.client.verbose {
diff --git a/internal/commands/root.go b/internal/commands/root.go
index a651612..0a0f174 100644
--- a/internal/commands/root.go
+++ b/internal/commands/root.go
@@ -29,20 +29,6 @@ func (e ExitCodeError) Error() string {
return fmt.Sprintf("exit code %d", e.Code)
}
-// Version info set by main via SetVersionInfo.
-var (
- versionStr = "dev"
- commitStr = "none"
- dateStr = "unknown"
-)
-
-// SetVersionInfo is called from main.go to inject ldflags values.
-func SetVersionInfo(version, commit, date string) {
- versionStr = version
- commitStr = commit
- dateStr = date
-}
-
// RunContext carries resolved dependencies into every command handler.
// No global state — everything a command needs is here.
type RunContext struct {
diff --git a/internal/commands/version.go b/internal/commands/version.go
index 11b602d..a74febc 100644
--- a/internal/commands/version.go
+++ b/internal/commands/version.go
@@ -4,6 +4,8 @@ import (
"fmt"
"github.com/spf13/cobra"
+
+ "github.com/workato-devs/wk/internal/version"
)
func newVersionCmd() *cobra.Command {
@@ -20,16 +22,16 @@ func newVersionCmd() *cobra.Command {
}
info := map[string]string{
- "version": versionStr,
- "commit": commitStr,
- "date": dateStr,
+ "version": version.Version(),
+ "commit": version.Commit(),
+ "date": version.Date(),
}
if flagJSON {
return rctx.Formatter.Format(cmd.OutOrStdout(), info)
}
- fmt.Fprintf(cmd.OutOrStdout(), "wk %s\n", versionStr)
+ fmt.Fprintf(cmd.OutOrStdout(), "wk %s\n", version.Version())
return nil
},
}
diff --git a/internal/mcp/client.go b/internal/mcp/client.go
index 1ed0e1e..24eb4c6 100644
--- a/internal/mcp/client.go
+++ b/internal/mcp/client.go
@@ -11,6 +11,7 @@ import (
"time"
"github.com/workato-devs/wk/internal/api"
+ "github.com/workato-devs/wk/internal/version"
)
// Client is an MCP protocol client using Streamable HTTP transport.
@@ -57,7 +58,7 @@ func (c *Client) Initialize(ctx context.Context) (*api.MCPServerInfo, error) {
"capabilities": map[string]any{},
"clientInfo": map[string]any{
"name": "wk",
- "version": "dev",
+ "version": version.Version(),
},
},
ID: 1,
@@ -125,6 +126,7 @@ func (c *Client) send(ctx context.Context, rpcReq jsonRPCRequest) (*jsonRPCRespo
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "text/event-stream, application/json")
+ req.Header.Set("User-Agent", version.UserAgent())
if c.sessionID != "" {
req.Header.Set("Mcp-Session-Id", c.sessionID)
}
diff --git a/internal/version/version.go b/internal/version/version.go
new file mode 100644
index 0000000..1acc19f
--- /dev/null
+++ b/internal/version/version.go
@@ -0,0 +1,55 @@
+// Package version holds build-time version metadata injected via goreleaser
+// ldflags (see .goreleaser.yaml and the Makefile) and derives values from it,
+// such as the HTTP User-Agent sent on every API request for backend telemetry.
+package version
+
+import (
+ "fmt"
+ "strings"
+)
+
+// These are populated once at startup by Set. The real values originate from
+// the Go linker's -X flags, which are declared in the Makefile and
+// .goreleaser.yaml, applied to main.version/commit/date, then handed here via
+// Set. They default to dev values so `go run`/`go test` builds (no -X flags)
+// still produce a sensible User-Agent.
+var (
+ version = "dev"
+ commit = "none"
+ date = "unknown"
+)
+
+// Set records the build-time version info. Called once from main, before any
+// HTTP client is constructed.
+func Set(v, c, d string) {
+ // Normalize a leading "v". Git tags are "v1.0.6-beta"; `git describe` in
+ // the Makefile keeps the "v", while goreleaser's {{.Version}} strips it.
+ // Trimming here keeps the version — and the telemetry User-Agent —
+ // consistent regardless of which build path produced the binary.
+ version = strings.TrimPrefix(v, "v")
+ commit = c
+ date = d
+}
+
+// Version returns the build version string (e.g. "1.2.3" or "dev").
+func Version() string { return version }
+
+// Commit returns the build commit hash.
+func Commit() string { return commit }
+
+// Date returns the build date.
+func Date() string { return date }
+
+// UserAgent returns the HTTP User-Agent header value, e.g. "wk-cli/1.2.3".
+// The backend telemetry pipeline attributes requests to the CLI and its
+// version via this header, mirroring the old workato-platform-cli's
+// "workato-platform-cli/" convention.
+//
+// The token is "wk-cli", not the bare command name "wk": the nginx access-log
+// field this lands in is analyzed/tokenized, so a 2-char token like "wk"
+// shows up as a stray token in unrelated user-agents and collides. "wk-cli"
+// filters cleanly as a phrase ("wk" then "cli" adjacent), and it matches the
+// token the original beta builds already sent, keeping telemetry continuous.
+func UserAgent() string {
+ return fmt.Sprintf("wk-cli/%s", version)
+}
diff --git a/internal/version/version_test.go b/internal/version/version_test.go
new file mode 100644
index 0000000..a0e1978
--- /dev/null
+++ b/internal/version/version_test.go
@@ -0,0 +1,47 @@
+package version
+
+import "testing"
+
+func TestSetPopulatesAccessors(t *testing.T) {
+ Set("1.2.3", "abc1234", "2026-06-30")
+
+ if got, want := Version(), "1.2.3"; got != want {
+ t.Errorf("Version() = %q, want %q", got, want)
+ }
+ if got, want := Commit(), "abc1234"; got != want {
+ t.Errorf("Commit() = %q, want %q", got, want)
+ }
+ if got, want := Date(), "2026-06-30"; got != want {
+ t.Errorf("Date() = %q, want %q", got, want)
+ }
+}
+
+func TestUserAgentFormat(t *testing.T) {
+ Set("1.2.3", "c", "d")
+
+ if got, want := UserAgent(), "wk-cli/1.2.3"; got != want {
+ t.Errorf("UserAgent() = %q, want %q", got, want)
+ }
+}
+
+// A leading "v" from a git tag (e.g. via `git describe` in the Makefile) must
+// be normalized so the telemetry token is consistent across build paths.
+func TestSetStripsLeadingV(t *testing.T) {
+ Set("v1.0.6-beta", "c", "d")
+
+ if got, want := Version(), "1.0.6-beta"; got != want {
+ t.Errorf("Version() = %q, want %q (leading v should be stripped)", got, want)
+ }
+ if got, want := UserAgent(), "wk-cli/1.0.6-beta"; got != want {
+ t.Errorf("UserAgent() = %q, want %q", got, want)
+ }
+}
+
+// The dev fallback must not be mangled by the "v" trimming.
+func TestSetDevFallbackUnchanged(t *testing.T) {
+ Set("dev", "none", "unknown")
+
+ if got, want := UserAgent(), "wk-cli/dev"; got != want {
+ t.Errorf("UserAgent() = %q, want %q", got, want)
+ }
+}