Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ notes and errors go to stderr.
vabc --json inventory check 010807 --near 22182 | jq '.store.quantity, .nearbyStores[].quantity'
```

`vabc` conforms to the [Agent CLI Guidelines](https://aclig.dev/) **v0.4.0** at the **Full** level.
The conformance block is machine-verifiable from the binary itself — `vabc schema --json` emits
`{ "spec": "agent-cli-guidelines", "version": "0.4.0", "level": "Full" }` — so an agent can confirm
the contract version without trusting this badge.

## How it works

Everything is a live read against Virginia ABC's public, unauthenticated endpoints — product
Expand Down
33 changes: 29 additions & 4 deletions internal/cli/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,11 @@ func (c *SchemaCmd) Run(rt *Runtime) error {
return errs.New(errs.ExitGeneric, "SCHEMA_ERROR", err.Error(), "")
}
out := map[string]any{
"tool": "vabc",
"version": version.String(),
"tool": "vabc",
"version": version.String(),
"conformance": map[string]any{
"spec": "agent-cli-guidelines", "version": version.Spec, "level": "Full",
},
"commands": nodeToMap(k.Model.Node),
"exit_codes": errs.Table(),
"safety": map[string]any{
Expand Down Expand Up @@ -159,8 +162,30 @@ func (c *AgentCmd) Run(rt *Runtime) error {

// --- version ----------------------------------------------------------------

type VersionCmd struct{}
// VersionCmd prints the version, or with --check asks (pull-based, fail-silent) whether a
// newer release exists. Update awareness, never self-mutation (contract §11). The tool never
// auto-updates; it only reports the upgrade command for the human/package manager.

type VersionCmd struct {
Check bool `help:"Check for a newer release (network, short timeout, fail-silent)."`
}

func (c *VersionCmd) Run(rt *Runtime) error {
return rt.Out.Emit(map[string]any{"version": version.String()})
cur := version.String()
if !c.Check {
return rt.Out.Emit(map[string]any{"version": cur})
}
out := map[string]any{
"current": cur,
"latest": nil,
"updateAvailable": false,
"upgrade": version.UpgradeHint(),
}
if latest, err := version.Latest(rt.Ctx); err == nil && latest != "" {
out["latest"] = latest
out["updateAvailable"] = version.UpdateAvailable(latest, cur)
} else {
out["note"] = "could not check for updates"
}
return rt.Out.Emit(out)
}
11 changes: 11 additions & 0 deletions internal/cli/testdata/schema.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,22 @@
"name": "agent"
},
{
"flags": [
{
"help": "Check for a newer release (network, short timeout, fail-silent).",
"name": "check"
}
],
"help": "Print the version.",
"name": "version"
}
]
},
"conformance": {
"level": "Full",
"spec": "agent-cli-guidelines",
"version": "0.4.0"
},
"exit_codes": {
"auth_required": 4,
"cancelled": 130,
Expand Down
82 changes: 81 additions & 1 deletion internal/version/version.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
package version

import "runtime/debug"
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"runtime/debug"
"strings"
"time"
)

// Spec is the Agent CLI Guidelines version this tool conforms to (declared in `schema`).
const Spec = "0.4.0"

// version is a plain literal so -ldflags "-X .../version.version=vX" can override it.
// It MUST NOT be initialized from a function call (golang/go#64246).
Expand All @@ -24,3 +36,71 @@ func String() string {
}
return version
}

// repoSlug parses "owner/repo" from the module path (github.com/owner/repo), or "" if
// it can't be determined.
func repoSlug() string {
if bi, ok := debug.ReadBuildInfo(); ok {
if rest, found := strings.CutPrefix(bi.Main.Path, "github.com/"); found {
parts := strings.Split(rest, "/")
if len(parts) >= 2 {
return parts[0] + "/" + parts[1]
}
}
}
return ""
}

// UpgradeHint returns the recommended upgrade command for this tool.
func UpgradeHint() string {
if slug := repoSlug(); slug != "" {
return "go install github.com/" + slug + "/cmd/vabc@latest"
}
return ""
}

// Latest returns the latest released version tag (from GitHub Releases by default).
// Network, short timeout, **fail-silent**: returns ("", err) on any problem so a
// `version --check` never errors or blocks an agent loop. The release source can be
// overridden with VABC_RELEASES_URL (used in tests).
func Latest(ctx context.Context) (string, error) {
url := os.Getenv("VABC_RELEASES_URL")
if url == "" {
slug := repoSlug()
if slug == "" {
return "", fmt.Errorf("unknown repository")
}
url = "https://api.github.com/repos/" + slug + "/releases/latest"
}
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return "", err
}
req.Header.Set("Accept", "application/vnd.github+json")
resp, err := http.DefaultClient.Do(req)
Comment on lines +81 to +87
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("release source: %s", resp.Status)
}
var body struct {
TagName string `json:"tag_name"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return "", err
}
return body.TagName, nil
}

// UpdateAvailable reports whether latest is a different release than current.
// Dev/source builds (current == "dev") never report an update — don't nag them.
func UpdateAvailable(latest, current string) bool {
if latest == "" || current == "" || current == "dev" {
return false
}
return strings.TrimPrefix(latest, "v") != strings.TrimPrefix(current, "v")
}
2 changes: 2 additions & 0 deletions site/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-check
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";
import starlightLlmsTxt from "starlight-llms-txt";

// Canonical site URL. Currently the live Vercel alias; cut over to https://vabc.sh
// once the domain is bought (update here + redeploy + re-alias + regenerate OG cards).
Expand All @@ -15,6 +16,7 @@ export default defineConfig({
description:
"Virginia ABC product search and store inventory from your terminal — agent-friendly, read-only.",
tagline: "Find the bottle. Skip the website.",
plugins: [starlightLlmsTxt()],
logo: { src: "./src/assets/mark.svg", replacesTitle: false },
customCss: ["./src/styles/tokens.css", "./src/styles/docs.css"],
social: { github: "https://github.com/rnwolfe/vabc" },
Expand Down
1 change: 1 addition & 0 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@astrojs/starlight": "^0.30.0",
"starlight-llms-txt": "^0.4.0",
"@fontsource/bricolage-grotesque": "^5.2.10",
Comment thread
Copilot marked this conversation as resolved.
Outdated
"@fontsource/ibm-plex-mono": "^5.2.7",
"@fontsource/ibm-plex-sans": "^5.2.8",
Expand Down
Loading
Loading