← back to Cli Printing Press
fix(cli): decouple CLI version from API version
68bc76f3ade620d696c08f60f9b8e738e7d9af65 · 2026-04-11 12:52:01 -0700 · Trevin Chow
Generated CLIs now always start at version 1.0.0 regardless of the
API spec's info.version. The API version is stored in the manifest's
api_version field for provenance only.
Previously the generator copied the spec version into the CLI's
--version output, causing confusion: a CLI at "4.4" looked mature
but was actually a fresh generation that inherited the API's version.
- Templates: hardcode `var version = "1.0.0"` instead of `{{.Version}}`
- Manifest: new `api_version` field preserves the spec version
- Validate: removed version normalization (no longer needed)
- Tests: updated to reflect pass-through behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files touched
M internal/generator/templates/plan_root.go.tmplM internal/generator/templates/root.go.tmplM internal/pipeline/climanifest.goM internal/spec/spec.goM internal/spec/spec_test.go
Diff
commit 68bc76f3ade620d696c08f60f9b8e738e7d9af65
Author: Trevin Chow <trevin@trevinchow.com>
Date: Sat Apr 11 12:52:01 2026 -0700
fix(cli): decouple CLI version from API version
Generated CLIs now always start at version 1.0.0 regardless of the
API spec's info.version. The API version is stored in the manifest's
api_version field for provenance only.
Previously the generator copied the spec version into the CLI's
--version output, causing confusion: a CLI at "4.4" looked mature
but was actually a fresh generation that inherited the API's version.
- Templates: hardcode `var version = "1.0.0"` instead of `{{.Version}}`
- Manifest: new `api_version` field preserves the spec version
- Validate: removed version normalization (no longer needed)
- Tests: updated to reflect pass-through behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
internal/generator/templates/plan_root.go.tmpl | 2 +-
internal/generator/templates/root.go.tmpl | 2 +-
internal/pipeline/climanifest.go | 6 ++++++
internal/spec/spec.go | 13 +++----------
internal/spec/spec_test.go | 15 ++++++++-------
5 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/internal/generator/templates/plan_root.go.tmpl b/internal/generator/templates/plan_root.go.tmpl
index b37a3da5..10b2889b 100644
--- a/internal/generator/templates/plan_root.go.tmpl
+++ b/internal/generator/templates/plan_root.go.tmpl
@@ -10,7 +10,7 @@ import (
"github.com/spf13/cobra"
)
-var version = "{{.Version}}"
+var version = "1.0.0"
// Execute runs the CLI.
func Execute() error {
diff --git a/internal/generator/templates/root.go.tmpl b/internal/generator/templates/root.go.tmpl
index 7ee7dfa4..508ddbec 100644
--- a/internal/generator/templates/root.go.tmpl
+++ b/internal/generator/templates/root.go.tmpl
@@ -15,7 +15,7 @@ import (
"github.com/spf13/cobra"
)
-var version = "{{.Version}}"
+var version = "1.0.0"
type rootFlags struct {
asJSON bool
diff --git a/internal/pipeline/climanifest.go b/internal/pipeline/climanifest.go
index 61bac45a..1e8bd9dc 100644
--- a/internal/pipeline/climanifest.go
+++ b/internal/pipeline/climanifest.go
@@ -48,6 +48,7 @@ type CLIManifest struct {
MCPToolCount int `json:"mcp_tool_count,omitempty"`
MCPPublicToolCount int `json:"mcp_public_tool_count,omitempty"`
MCPReady string `json:"mcp_ready,omitempty"`
+ APIVersion string `json:"api_version,omitempty"` // from the spec's info.version — provenance only, not the CLI version
AuthType string `json:"auth_type,omitempty"`
AuthEnvVars []string `json:"auth_env_vars,omitempty"`
NovelFeatures []NovelFeatureManifest `json:"novel_features,omitempty"`
@@ -198,6 +199,11 @@ func WriteManifestForGenerate(p GenerateManifestParams) error {
m.Description = entry.Description
}
+ // Record the API version from the spec for provenance (not the CLI version).
+ if p.Spec != nil && p.Spec.Version != "" {
+ m.APIVersion = p.Spec.Version
+ }
+
// Populate MCP metadata from the parsed spec.
if p.Spec != nil {
populateMCPMetadata(&m, p.Spec)
diff --git a/internal/spec/spec.go b/internal/spec/spec.go
index e7abde8b..b9347eb3 100644
--- a/internal/spec/spec.go
+++ b/internal/spec/spec.go
@@ -274,16 +274,9 @@ func (s *APISpec) Validate() error {
if s.Name == "" {
return fmt.Errorf("name is required")
}
- // Default version to 1.0.0 if missing, and normalize to semver.
- if s.Version == "" {
- s.Version = "1.0.0"
- } else if !strings.Contains(s.Version, ".") {
- // Bare major: "4" → "4.0.0"
- s.Version = s.Version + ".0.0"
- } else if strings.Count(s.Version, ".") == 1 {
- // Major.minor only: "4.4" → "4.4.0"
- s.Version = s.Version + ".0"
- }
+ // Note: s.Version holds the API version from the spec (for provenance).
+ // The CLI version is always hardcoded to "1.0.0" in the generated root.go
+ // template — it is independent of the API version.
// Parser fallback may supply a placeholder base_url when the source spec omits servers.
if s.BaseURL == "" && s.BasePath == "" {
return fmt.Errorf("base_url is required")
diff --git a/internal/spec/spec_test.go b/internal/spec/spec_test.go
index 693320de..95537e7d 100644
--- a/internal/spec/spec_test.go
+++ b/internal/spec/spec_test.go
@@ -87,7 +87,7 @@ func TestValidation(t *testing.T) {
}
}
-func TestVersionDefaultAndNormalize(t *testing.T) {
+func TestVersionPassedThrough(t *testing.T) {
base := func(v string) APISpec {
return APISpec{
Name: "x",
@@ -97,16 +97,17 @@ func TestVersionDefaultAndNormalize(t *testing.T) {
}
}
+ // Version is the API version (provenance only). It passes through as-is.
+ // The CLI version is hardcoded to "1.0.0" in the generated root.go template.
tests := []struct {
input string
expected string
}{
- {"", "1.0.0"}, // empty → default
- {"1.0.0", "1.0.0"}, // already semver
- {"4", "4.0.0"}, // bare major
- {"4.4", "4.4.0"}, // major.minor only
- {"4.17.1", "4.17.1"}, // already semver
- {"0.1.0", "0.1.0"}, // already semver
+ {"", ""}, // empty stays empty
+ {"1.0.0", "1.0.0"}, // semver preserved
+ {"4", "4"}, // non-semver API versions preserved
+ {"4.4", "4.4"}, // major.minor preserved
+ {"4.17.1", "4.17.1"}, // full semver preserved
}
for _, tt := range tests {
← f07a795d fix(cli): default empty version to 1.0.0 and normalize to se
·
back to Cli Printing Press
·
fix(ci): auto-sync go install when installed binary exists 2e21807d →