version

package
v0.22.152 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 2, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package version — `clawtool version --check` runner.

Wraps CheckForUpdate with CLI-shaped output + exit codes so a monitoring script (CI cron, system-package wrapper, fleet inventory) can pipeline-detect out-of-date clawtool installs without parsing human banners. The cache + GitHub-API logic lives in update.go; this file is purely the rendering / exit-code layer.

Exit-code contract (stable, scripts will gate on it):

0 — up-to-date (running binary's version >= latest tag)
1 — newer release available
2 — check itself failed (network, parse, rate-limit)

Anyone changing these values needs to rev the wiki/decisions doc that promises them — they're now part of the public CLI contract.

internal/version/manifestsync.go — pure helpers that rewrite the .claude-plugin/plugin.json + .claude-plugin/marketplace.json manifests from a supplied semver string.

Lives in package version (not cmd/version-sync) so the codegen invariant test (release_pipeline_test.go) can call the same helpers the binary uses without spawning a subprocess. The `cmd/version-sync` main is a thin wrapper that wires version.Version into these helpers and writes the result to disk.

Implementation note: we deliberately use a regex-style substitution rather than json.Marshal-with-an-ordered-map. The Claude Code marketplace.json + plugin.json carry a specific field order that reviewers expect to see preserved (mcpServers last, etc.), and re-marshalling through Go's encoding/json would alphabetize map keys and lose the trailing newline / 2-space indent the existing files use. The regex approach guarantees byte-identical output except for the version line(s) — which is exactly what the `git diff --exit-code` CI gate expects.

Package version — daemon-side periodic update poller. Every `Interval` ticks (default 1h) the poller calls `CheckForUpdate`; when a transition from no-update → update-available is detected it broadcasts a SystemNotification onto the supplied publisher (typically biam.WatchHub.BroadcastSystem). Connected watchers — orchestrator, dashboard, `task watch`, MCP clients dialling the watch socket — render the inline banner immediately, no polling.

Why daemon-side rather than per-CLI: the CLI is short-lived; the daemon (`clawtool serve`) is the long-running process the operator already keeps up. One canonical poller, single GitHub round-trip per host per hour, push to every active surface.

Telemetry: each transition emits a `clawtool.update_check` event with the same allow-listed payload SessionStart uses, so the operator gets a unified PostHog view of update detection across surfaces.

internal/version/sync.go — go:generate hook for the version-sync codegen.

Running `go generate ./internal/version/...` (or the `make sync-versions` Makefile alias) rewrites the two .claude-plugin manifests from the canonical Version variable in version.go. Operators bumping the const should run the generator + commit the regenerated manifests in the same changeset; the TestReleasePipeline_VersionsAreCodegenSynced test fails CI if they drift.

The generator is implemented as a separate main package (cmd/version-sync) rather than a //go:build-tagged tool inside this package because (1) it has its own dependency on os/exec and filepath traversal that would bloat the version package's import surface, and (2) `go run` against a sibling cmd/ keeps the generator runnable from a tarball without `go install`-ing anything globally.

Package version — update-check primitive. Hits the GitHub Releases API for the latest tag and reports whether the local build is older. Cached for 24h in ~/.cache/clawtool/update.json so a `clawtool doctor` (or any other surface that calls CheckForUpdate) doesn't pay a network roundtrip on every invocation.

No background polling, no telemetry — this is a stateless, user-initiated check. The cache exists purely to avoid being rude to the GitHub API.

Package version exposes the clawtool build version.

`Info()` and `InfoJSON()` extend the surface for shell-pipeline consumers that want a structured snapshot (`clawtool version --json`) instead of the human banner. Single source of truth so telemetry / health-probe / version-gate scripts all read the same shape.

Three layers, picked in order:

  1. ldflags override — `go build -ldflags='-X github.com/cogitave/clawtool/internal/version.Version=v…'`. goreleaser sets this on every release tarball, so installed binaries always carry the exact tag.

  2. runtime/debug.ReadBuildInfo — module-cached `go install` binaries surface the tag here. Local `go build` from a working tree returns "(devel)".

  3. The release-please-tracked constant below — fallback for dev workflows where neither (1) nor (2) yields a real version.

`Resolved()` is the single function every caller (overview, upgrade, claude-bootstrap, telemetry) must use to read the effective version. Reading `Version` directly (the constant) will diverge from what the binary actually is when goreleaser stamped a different value via ldflags.

History: pre-2026-05-01 there was a literal-string sentinel (`Version != "0.21.7"`) acting as the "this is the dev fallback, prefer debug.BuildInfo" gate. release-please bumped the const past that literal during the v0.22.x stretch and the sentinel quietly stopped firing — every dev binary then took the const branch even when debug.BuildInfo had a real install tag. The fix: drop the sentinel entirely. The const IS the canonical version (TestReleasePipeline_VersionStringsInSync pins it equal to plugin.json + marketplace.json), so trust it. debug.BuildInfo only matters as a probe-fallback when somehow Version was zeroed (e.g. an explicit `-X .Version=""`); the pseudo-version regex still filters Go's noisy `v0.0.0-<ts>-<sha>` install shape.

Index

Constants

View Source
const Name = "clawtool"

x-release-please-start-version

View Source
const UpdateCheckURL = "https://api.github.com/repos/cogitave/clawtool/releases/latest"

UpdateCheckURL is the GitHub Releases API endpoint we hit. The public API permits ~60 unauthenticated requests/hour per IP; the 24h cache keeps us well under that even on shared CI runners.

Variables

View Source
var Version = "0.22.119" // x-release-please-version

Version is the build-stamped semver string. Declared as `var` (not `const`) so goreleaser can override it via `-ldflags='-X github.com/cogitave/clawtool/internal/version.Version=…'` at link time. `-X` cannot patch constants; that's why this is a var even though it's effectively immutable at runtime.

Functions

func InfoJSON added in v0.22.40

func InfoJSON() (string, error)

InfoJSON renders Info() as indented JSON, ready to print. The indented form is the same shape Marshal would emit; we use MarshalIndent because operators inspecting the output by eye expect a pretty document, and shell pipelines (`jq`) handle either form transparently.

func Resolved added in v0.22.8

func Resolved() string

Resolved returns the authoritative installed-binary version. First-call computation is cached for the process lifetime — the binary's version doesn't change while it's running.

Output strips any leading "v" so callers can pass it straight into Compare() without normalising at every call site.

**Every external surface MUST use this** — telemetry events, hook payloads, /v1/health JSON, A2A card, doctor banner, orchestrator probe, MCP serverInfo. The literal `Version` var holds the release-please-tracked release tag and stays in sync with .claude-plugin/plugin.json + .claude-plugin/marketplace.json (cmd/version-sync regenerates both manifests from the const, and TestReleasePipeline_VersionStringsInSync gates drift). Reading the bare const still risks bypassing a goreleaser-stamped ldflags override on a snapshot build, so route through Resolved() unless you have a reason. The bug repeated across 9 sites before the operator caught it on 2026-04-29 ("12 hours, no telemetry events"). Don't repeat it — call Resolved().

func RunCheck added in v0.22.40

func RunCheck(ctx context.Context, jsonOutput bool, w io.Writer) int

RunCheck performs an update probe, renders the result to w, and returns the CLI exit code. When jsonOutput is true the rendered form is the indented JSON marshal of CheckResult; otherwise a short human banner that names the current + latest versions and points at `clawtool upgrade` when there's drift.

The probe routes through CheckForUpdate, which honours the 5-minute on-disk cache so scripts that gate on this every loop don't hammer the GitHub API.

func String

func String() string

String is the formatted "clawtool X.Y.Z" banner the CLI prints.

func SyncMarketplaceJSON added in v0.22.126

func SyncMarketplaceJSON(in []byte, ver string) ([]byte, error)

SyncMarketplaceJSON rewrites marketplace.json's two "version" fields (metadata.version + plugins[0].version) to the supplied semver. Asserts exactly two matches so a schema change that adds or removes a "version" occurrence trips this fence instead of silently mis-syncing.

func SyncPluginJSON added in v0.22.126

func SyncPluginJSON(in []byte, ver string) ([]byte, error)

SyncPluginJSON rewrites plugin.json's single top-level "version" field to the supplied semver. plugin.json carries one version occurrence; we replace all matches but assert exactly one was found so a future schema change that adds a second "version" key fails loudly instead of silently rewriting too much.

Types

type BuildInfo added in v0.22.40

type BuildInfo struct {
	Name      string `json:"name"`
	Version   string `json:"version"`
	GoVersion string `json:"go_version"`
	Platform  string `json:"platform"` // GOOS/GOARCH
	// Commit is the git revision baked in by `go build` via
	// debug.ReadBuildInfo. Empty when the binary was built without
	// VCS info (e.g. via the goreleaser pipeline that strips it).
	Commit string `json:"commit,omitempty"`
	// Modified reports whether the working tree was dirty when the
	// binary was built. Best-effort — only populated when the build
	// captured VCS info.
	Modified bool `json:"modified,omitempty"`
}

BuildInfo is the structured snapshot emitted by `clawtool version --json`. Single source of truth for any external probe (telemetry, /v1/health, monitoring scripts) that wants to reason about the running binary's identity. snake_case JSON tags follow the project's convention (mirrors the agents.Status / agentListEntry shape from earlier ticks).

func Info added in v0.22.40

func Info() BuildInfo

Info returns a structured snapshot of the running binary. Wraps Resolved() so the version string respects the same ldflags / debug.BuildInfo / fallback hierarchy.

type CheckResult added in v0.22.40

type CheckResult struct {
	Up        bool      `json:"up_to_date"`
	Current   string    `json:"current"`
	Latest    string    `json:"latest,omitempty"`
	FetchedAt time.Time `json:"fetched_at"`
	Error     string    `json:"error,omitempty"`
}

CheckResult is the structured payload of a `version --check` invocation. snake_case JSON tags follow the project-wide wire convention (mirrors BuildInfo / agents.Status / agentListEntry).

`Up` collapses HasUpdate + Err into the binary "are we current?" signal a script actually wants — a check failure is *not* an affirmative "up to date" answer (Up=false, Error!="" → exit 2).

func (CheckResult) ExitCode added in v0.22.40

func (r CheckResult) ExitCode() int

ExitCode classifies a result into the documented CLI exit codes (0 / 1 / 2). Centralised here so both RunCheck and any future callers (HTTP probe, scripted wrapper) agree on the mapping.

type Poller added in v0.22.9

type Poller struct {
	// contains filtered or unexported fields
}

Poller wraps the periodic update probe + publisher. Lifetime = daemon process. Stop via ctx cancellation.

func NewPoller added in v0.22.9

func NewPoller(pub PublishFn, cfg PollerConfig, track func(outcome string)) *Poller

NewPoller constructs the poller with the given publisher and optional telemetry tracker. `track` is called on every check with the outcome enum ("up_to_date" | "update_available" | "check_failed"); pass nil to skip telemetry.

func (*Poller) Run added in v0.22.9

func (p *Poller) Run(ctx context.Context)

Run blocks until ctx cancels, ticking once per Interval. The first check fires immediately so a fresh daemon catches an already-pending update without waiting an hour.

type PollerConfig added in v0.22.9

type PollerConfig struct {
	// Interval between checks. Default 1h. Tests pass 50ms.
	Interval time.Duration
	// Timeout per HTTP round-trip. Default 5s.
	Timeout time.Duration
	// Now overrides time.Now for deterministic testing of
	// transitions. Production passes nil.
	Now func() time.Time
}

PollerConfig overrides the defaults — useful for tests that need a tighter tick. Empty struct = production defaults.

type PublishFn added in v0.22.9

type PublishFn func(kind, severity, title, body, actionHint string)

PublishFn is the slim function shape the poller needs from the caller. server.go wraps biam.WatchHub.BroadcastSystem; tests pass a recorder closure. Keeping this as a function instead of an interface avoids dragging biam into the version package's import graph (version stays a leaf).

type UpdateInfo added in v0.9.0

type UpdateInfo struct {
	// HasUpdate is true when the upstream tag is newer than the
	// local build. Defaults to false on any error so we never nag
	// the user about a non-confirmed update.
	HasUpdate bool

	// Latest is the tag string from GitHub (e.g. "v0.10.0"). Empty
	// on error.
	Latest string

	// Current is the local build version as Resolved() returns it
	// — i.e. the goreleaser-stamped tag when present, else the
	// debug.BuildInfo Main.Version, else the package fallback.
	// Reading the bare `Version` var here would mis-report on every
	// production binary (it carries the dev-fallback "0.21.7"); a
	// HasUpdate=true would fire for already-current installs.
	Current string

	// FetchedAt is when we last asked GitHub. Surfaced so the user
	// knows how stale the answer is. UTC.
	FetchedAt time.Time

	// Err is non-nil when the check itself failed (network, parse,
	// rate-limit). Callers display "could not check" instead of
	// pretending no update exists.
	Err error
}

UpdateInfo is the result a caller surfaces in the UI.

func CheckForUpdate added in v0.9.0

func CheckForUpdate(ctx context.Context) UpdateInfo

CheckForUpdate is the entry point. Returns the cached result when fresh; otherwise hits the API, persists, returns. Never blocks longer than the HTTP client's timeout.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL