diff --git a/CHANGELOG.md b/CHANGELOG.md index 67c9360..16ea2fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 0.4.1 - Unreleased +- Add cached release checks with `gitcrawl check-update` and passive terminal + notices when a newer OpenClaw release is available. + ## 0.4.0 - 2026-05-17 - Harden portable-store publishing and reads with manifest integrity checks, temp-DB validation before runtime replacement, stale Git lock cleanup, reclone fallback, and richer `doctor` DB health output. diff --git a/README.md b/README.md index 6c4e755..839be72 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,17 @@ go build -ldflags "-X github.com/openclaw/gitcrawl/internal/cli.version=$(git de ./bin/gitcrawl --version ``` +Check for newer releases manually with: + +```bash +gitcrawl check-update +``` + +Interactive terminal runs also perform a cached daily release check and print a +stderr notice when a newer OpenClaw release is available. Set +`GITCRAWL_NO_UPDATE_CHECK=1` or `CRAWLKIT_NO_UPDATE_CHECK=1` to disable that +passive notice. + Docker: ```bash diff --git a/docs/installation.md b/docs/installation.md index 4692c52..ed9a060 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -45,6 +45,18 @@ gitcrawl --version Browse the [releases page](https://github.com/openclaw/gitcrawl/releases) for the latest tag and the full asset list. Use a directory that is already on your `PATH`; `~/bin` and `~/.local/bin` avoid needing elevated permissions. +## Check for updates + +```bash +gitcrawl check-update +gitcrawl check-update --json +``` + +Interactive terminal runs perform a cached daily release check and print a +stderr notice when a newer OpenClaw release is available. Scripted, JSON, CI, +and non-TTY runs skip the passive notice. Set `GITCRAWL_NO_UPDATE_CHECK=1` or +`CRAWLKIT_NO_UPDATE_CHECK=1` to disable it. + ## Install from source ```bash diff --git a/go.mod b/go.mod index fc45c5d..2d61581 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/charmbracelet/x/ansi v0.11.7 github.com/mattn/go-isatty v0.0.22 - github.com/openclaw/crawlkit v0.5.3 + github.com/openclaw/crawlkit v0.6.0 ) require ( diff --git a/go.sum b/go.sum index a7e4ed0..fe7a19d 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/openclaw/crawlkit v0.5.3 h1:bRfix5IaajxsN9QAExkW8qMM7uVaT5yXnQpSI1Qu3SI= -github.com/openclaw/crawlkit v0.5.3/go.mod h1:+2kUqiyecwK/vWySHqZnu968wmdWGBebJ22uUhQEalM= +github.com/openclaw/crawlkit v0.6.0 h1:7ef1UDl6qrV3KJu74mw2WJn1+G5zEMhzTFaktn0CMZ4= +github.com/openclaw/crawlkit v0.6.0/go.mod h1:+2kUqiyecwK/vWySHqZnu968wmdWGBebJ22uUhQEalM= github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/internal/cli/app.go b/internal/cli/app.go index a72cfee..1b0d921 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -125,10 +125,13 @@ func (a *App) Run(ctx context.Context, args []string) error { a.printUsage() return nil } + a.maybeNotifyRelease(ctx, rest) switch rest[0] { case "version": return a.writeOutput("version", map[string]string{"version": version}, false) + case "check-update": + return a.runCheckUpdate(ctx, rest[1:]) case "metadata": return a.runMetadata(rest[1:]) case "serve": @@ -2843,6 +2846,7 @@ func (a *App) runMetadata(args []string) error { manifest.Privacy = control.Privacy{ContainsPrivateMessages: false, ExportsSecrets: false, LocalOnlyScopes: []string{"github", "sqlite", "portable"}} manifest.Commands = map[string]control.Command{ "status": {Title: "Status", Argv: []string{"gitcrawl", "status", "--json"}, JSON: true}, + "check-update": {Title: "Check for updates", Argv: []string{"gitcrawl", "check-update", "--json"}, JSON: true}, "doctor": {Title: "Doctor", Argv: []string{"gitcrawl", "doctor", "--json"}, JSON: true}, "sync": {Title: "Sync repository", Argv: []string{"gitcrawl", "sync", "--json"}, JSON: true, Mutates: true}, "search": {Title: "Search", Argv: []string{"gitcrawl", "search", "--json"}, JSON: true}, @@ -3588,6 +3592,7 @@ Global flags: Core commands: metadata print crawlkit control metadata + check-update check for a newer gitcrawl release status print fast read-only archive status init create config, optionally from a portable store doctor check config, token, and database readiness @@ -3629,6 +3634,11 @@ Usage: Usage: gitcrawl status [--json] +`, + "check-update": `gitcrawl check-update checks GitHub Releases for a newer gitcrawl build. + +Usage: + gitcrawl check-update [--json] [--force] `, "init": `gitcrawl init creates a local config and SQLite database. diff --git a/internal/cli/releasecheck.go b/internal/cli/releasecheck.go new file mode 100644 index 0000000..044d383 --- /dev/null +++ b/internal/cli/releasecheck.go @@ -0,0 +1,61 @@ +package cli + +import ( + "context" + "errors" + "flag" + "fmt" + + "github.com/openclaw/crawlkit/releasecheck" + "github.com/openclaw/gitcrawl/internal/config" +) + +const gitcrawlUpgradeHint = "brew upgrade openclaw/tap/gitcrawl" + +func gitcrawlReleaseCheckOptions(force bool) releasecheck.Options { + cfg := config.Default() + return releasecheck.Options{ + AppName: "gitcrawl", + Owner: "openclaw", + Repo: "gitcrawl", + CurrentVersion: version, + CacheDir: cfg.CacheDir, + Force: force, + } +} + +func (a *App) maybeNotifyRelease(ctx context.Context, args []string) { + _, _ = releasecheck.Notify(ctx, releasecheck.NotifyOptions{ + Options: gitcrawlReleaseCheckOptions(false), + Stderr: a.Stderr, + InstallHint: gitcrawlUpgradeHint, + Args: args, + JSONOutput: a.format == FormatJSON, + IsTerminal: releasecheck.StderrIsTerminal(), + }) +} + +func (a *App) runCheckUpdate(ctx context.Context, args []string) error { + fs := flag.NewFlagSet("check-update", flag.ContinueOnError) + fs.SetOutput(a.Stderr) + jsonOut := fs.Bool("json", false, "write JSON output") + force := fs.Bool("force", false, "force a fresh release check") + if err := fs.Parse(args); err != nil { + return usageErr(err) + } + if fs.NArg() != 0 { + return usageErr(fmt.Errorf("check-update takes flags only")) + } + result, err := releasecheck.Check(ctx, gitcrawlReleaseCheckOptions(*force)) + if err != nil && !errors.Is(err, releasecheck.ErrSkipped) { + return err + } + if *jsonOut || a.format == FormatJSON { + if *jsonOut { + a.format = FormatJSON + } + return a.writeOutput("check-update", result, false) + } + _, err = fmt.Fprint(a.Stdout, releasecheck.StatusText("gitcrawl", gitcrawlUpgradeHint, result)) + return err +}