Skip to content

Commit 0443100

Browse files
wesmclaude
andcommitted
feat: add self-update command and install scripts
Add `agentsview update` command that checks GitHub releases for newer versions, downloads with SHA256 verification, and performs atomic binary replacement. - internal/update: self-update package with cache, semver comparison, tar.gz/zip extraction, path traversal protection - cmd/agentsview/update.go: CLI integration with --check, --yes, --force - scripts/install.sh: Unix installer (macOS, Linux) with checksum verify - scripts/install.ps1: Windows installer with checksum verify - release.yml: produce .zip for Windows, include commit/buildDate in LDFLAGS, handle mixed archive types in checksums Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e426d43 commit 0443100

9 files changed

Lines changed: 1599 additions & 5 deletions

File tree

.github/workflows/release.yml

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,23 +77,32 @@ jobs:
7777
CC: ${{ matrix.cc || '' }}
7878
run: |
7979
VERSION=${GITHUB_REF#refs/tags/v}
80+
COMMIT=${GITHUB_SHA::8}
81+
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
8082
EXT=""
8183
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
8284
8385
mkdir -p dist
84-
LDFLAGS="-s -w -X main.version=v${VERSION}"
86+
LDFLAGS="-s -w -X main.version=v${VERSION} -X main.commit=${COMMIT} -X main.buildDate=${BUILD_DATE}"
8587
go build -tags fts5 -ldflags="$LDFLAGS" -trimpath \
8688
-o dist/agentsview${EXT} ./cmd/agentsview
8789
8890
cd dist
89-
ARCHIVE="agentsview_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}.tar.gz"
90-
tar czf "$ARCHIVE" agentsview${EXT}
91+
if [ "$GOOS" = "windows" ]; then
92+
ARCHIVE="agentsview_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}.zip"
93+
7z a "$ARCHIVE" agentsview${EXT}
94+
else
95+
ARCHIVE="agentsview_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}.tar.gz"
96+
tar czf "$ARCHIVE" agentsview${EXT}
97+
fi
9198
rm agentsview${EXT}
9299
93100
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
94101
with:
95102
name: agentsview-${{ matrix.goos }}-${{ matrix.goarch }}
96-
path: dist/*.tar.gz
103+
path: |
104+
dist/*.tar.gz
105+
dist/*.zip
97106
98107
release:
99108
needs: build
@@ -113,7 +122,7 @@ jobs:
113122
- name: Generate checksums
114123
run: |
115124
cd artifacts
116-
sha256sum *.tar.gz > SHA256SUMS
125+
sha256sum *.tar.gz *.zip > SHA256SUMS
117126
cat SHA256SUMS
118127
119128
- name: Get tag message
@@ -145,6 +154,7 @@ jobs:
145154
with:
146155
files: |
147156
artifacts/*.tar.gz
157+
artifacts/*.zip
148158
artifacts/SHA256SUMS
149159
body: ${{ steps.tag_message.outputs.has_body == 'true' && steps.tag_message.outputs.body || '' }}
150160
generate_release_notes: ${{ steps.tag_message.outputs.has_body != 'true' }}

cmd/agentsview/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ func main() {
3737
case "prune":
3838
runPrune(os.Args[2:])
3939
return
40+
case "update":
41+
runUpdate(os.Args[2:])
42+
return
4043
case "serve":
4144
runServe(os.Args[2:])
4245
return
@@ -63,6 +66,7 @@ Usage:
6366
agentsview [flags] Start the server (default command)
6467
agentsview serve [flags] Start the server (explicit)
6568
agentsview prune [flags] Delete sessions matching filters
69+
agentsview update [flags] Check for and install updates
6670
agentsview version Show version information
6771
agentsview help Show this help
6872
@@ -79,6 +83,11 @@ Prune flags:
7983
-dry-run Show what would be pruned without deleting
8084
-yes Skip confirmation prompt
8185
86+
Update flags:
87+
-check Check for updates without installing
88+
-yes Install without confirmation prompt
89+
-force Force check (ignore cache)
90+
8291
Environment variables:
8392
CLAUDE_PROJECTS_DIR Claude Code projects directory
8493
CODEX_SESSIONS_DIR Codex sessions directory

cmd/agentsview/update.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"flag"
6+
"fmt"
7+
"log"
8+
"os"
9+
"strings"
10+
11+
"github.com/wesm/agentsview/internal/config"
12+
"github.com/wesm/agentsview/internal/update"
13+
)
14+
15+
func runUpdate(args []string) {
16+
fs := flag.NewFlagSet("update", flag.ExitOnError)
17+
check := fs.Bool("check", false,
18+
"Check for updates without installing")
19+
yes := fs.Bool("yes", false,
20+
"Install without confirmation prompt")
21+
force := fs.Bool("force", false,
22+
"Force check (ignore cache)")
23+
fs.Usage = func() {
24+
fmt.Fprintln(fs.Output(),
25+
"Usage: agentsview update [flags]")
26+
fmt.Fprintln(fs.Output(), "\nFlags:")
27+
fs.PrintDefaults()
28+
}
29+
if err := fs.Parse(args); err != nil {
30+
log.Fatalf("parsing flags: %v", err)
31+
}
32+
33+
dataDir, err := config.ResolveDataDir()
34+
if err != nil {
35+
log.Fatalf("resolving data dir: %v", err)
36+
}
37+
38+
info, err := update.CheckForUpdate(
39+
version, *force, dataDir,
40+
)
41+
if err != nil {
42+
log.Fatalf("checking for updates: %v", err)
43+
}
44+
45+
if info == nil {
46+
fmt.Printf(
47+
"agentsview %s is up to date.\n", version,
48+
)
49+
return
50+
}
51+
52+
if info.IsDevBuild {
53+
fmt.Printf(
54+
"Running dev build (%s). "+
55+
"Latest release: %s\n",
56+
info.CurrentVersion, info.LatestVersion,
57+
)
58+
if *check {
59+
return
60+
}
61+
} else {
62+
fmt.Printf(
63+
"Update available: %s -> %s",
64+
info.CurrentVersion, info.LatestVersion,
65+
)
66+
if info.Size > 0 {
67+
fmt.Printf(
68+
" (%s)", update.FormatSize(info.Size),
69+
)
70+
}
71+
fmt.Println()
72+
if *check {
73+
return
74+
}
75+
}
76+
77+
if !*yes {
78+
fmt.Print("Install update? [y/N] ")
79+
reader := bufio.NewReader(os.Stdin)
80+
answer, _ := reader.ReadString('\n')
81+
answer = strings.TrimSpace(strings.ToLower(answer))
82+
if answer != "y" && answer != "yes" {
83+
fmt.Println("Update cancelled.")
84+
return
85+
}
86+
}
87+
88+
progressFn := func(downloaded, total int64) {
89+
if total > 0 {
90+
pct := float64(downloaded) / float64(total) * 100
91+
fmt.Printf(
92+
"\r %s / %s (%.0f%%)",
93+
update.FormatSize(downloaded),
94+
update.FormatSize(total),
95+
pct,
96+
)
97+
}
98+
}
99+
100+
if err := update.PerformUpdate(info, progressFn); err != nil {
101+
fmt.Println()
102+
log.Fatalf("update failed: %v", err)
103+
}
104+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ require (
1111
require (
1212
github.com/tidwall/match v1.1.1 // indirect
1313
github.com/tidwall/pretty v1.2.0 // indirect
14+
golang.org/x/mod v0.33.0 // indirect
1415
golang.org/x/sys v0.13.0 // indirect
1516
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
88
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
99
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
1010
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
11+
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
12+
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
1113
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
1214
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

0 commit comments

Comments
 (0)