Skip to content

Commit 9ea28ab

Browse files
FelixIsaacclaude
andcommitted
Add check-update cmd, update README
- New check-update cmd queries GitHub API for latest release - Add Linux install instructions - Add Updating section - Fix plugins exclusion docs (plugins/cache + plugins/marketplaces) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3df4cf0 commit 9ea28ab

3 files changed

Lines changed: 158 additions & 1 deletion

File tree

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,28 @@ scoop install claude-code-sync
4646
go install github.com/felixisaac/claude-code-sync@latest
4747
```
4848

49+
**Linux (manual):**
50+
```bash
51+
# Download latest release (adjust arch: amd64 or arm64)
52+
curl -sL https://github.com/felixisaac/claude-code-sync/releases/latest/download/claude-code-sync_linux_amd64.tar.gz | tar xz
53+
sudo mv claude-code-sync /usr/local/bin/
54+
```
55+
4956
**Manual:** Download the latest binary from [GitHub Releases](https://github.com/felixisaac/claude-code-sync/releases).
5057

58+
### Updating
59+
60+
```bash
61+
# Check for updates
62+
claude-code-sync check-update
63+
64+
# Update via package manager
65+
brew upgrade claude-code-sync # macOS
66+
scoop update claude-code-sync # Windows
67+
68+
# Or download latest from GitHub Releases
69+
```
70+
5171
### First Time Setup
5272

5373
```bash
@@ -95,6 +115,7 @@ claude-code-sync pull
95115
| `reset [--keep-key]` | Delete all sync data |
96116
| `unlink` | Disconnect from remote repo (keep local data) |
97117
| `version` | Show version |
118+
| `check-update` | Check for newer version |
98119
| `help` | Show help |
99120

100121
## What Gets Synced
@@ -124,7 +145,8 @@ claude-code-sync pull
124145
- `debug/` - Debug logs
125146
- `file-history/` - File history cache
126147
- `ide/` - IDE integration cache
127-
- `plugins/` - Plugin cache
148+
- `plugins/cache/` - Plugin cache
149+
- `plugins/marketplaces/` - Marketplace cache (installed_plugins.json and known_marketplaces.json ARE synced)
128150
- `shell-snapshots/` - Shell state snapshots
129151
- `telemetry/` - Telemetry data
130152
- `stats-cache.json` - Statistics cache

internal/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func init() {
3737
rootCmd.AddCommand(resetCmd)
3838
rootCmd.AddCommand(unlinkCmd)
3939
rootCmd.AddCommand(doctorCmd)
40+
rootCmd.AddCommand(checkUpdateCmd)
4041
}
4142

4243
// UI helpers

internal/cmd/update.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"runtime"
8+
"strings"
9+
"time"
10+
11+
"github.com/fatih/color"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
const (
16+
repoOwner = "felixisaac"
17+
repoName = "claude-code-sync"
18+
)
19+
20+
type githubRelease struct {
21+
TagName string `json:"tag_name"`
22+
HTMLURL string `json:"html_url"`
23+
Assets []struct {
24+
Name string `json:"name"`
25+
BrowserDownloadURL string `json:"browser_download_url"`
26+
} `json:"assets"`
27+
}
28+
29+
var checkUpdateCmd = &cobra.Command{
30+
Use: "check-update",
31+
Short: "Check for newer version",
32+
RunE: runCheckUpdate,
33+
}
34+
35+
func runCheckUpdate(cmd *cobra.Command, args []string) error {
36+
logInfo("Checking for updates...")
37+
38+
latest, err := getLatestRelease()
39+
if err != nil {
40+
return fmt.Errorf("failed to check for updates: %w", err)
41+
}
42+
43+
latestVer := strings.TrimPrefix(latest.TagName, "v")
44+
currentVer := version
45+
46+
if latestVer == currentVer {
47+
logSuccess(fmt.Sprintf("You're on the latest version (v%s)", currentVer))
48+
return nil
49+
}
50+
51+
// Simple version comparison (works for semver)
52+
if compareVersions(latestVer, currentVer) > 0 {
53+
fmt.Println()
54+
color.Yellow("Update available: v%s → v%s", currentVer, latestVer)
55+
fmt.Println()
56+
fmt.Println("Download:")
57+
fmt.Printf(" %s\n", latest.HTMLURL)
58+
fmt.Println()
59+
60+
// Show direct download link for current platform
61+
assetName := getAssetName()
62+
for _, asset := range latest.Assets {
63+
if asset.Name == assetName {
64+
fmt.Println("Direct download for your platform:")
65+
fmt.Printf(" %s\n", asset.BrowserDownloadURL)
66+
break
67+
}
68+
}
69+
fmt.Println()
70+
logInfo("To update: download and replace your current binary")
71+
} else {
72+
logSuccess(fmt.Sprintf("You're on the latest version (v%s)", currentVer))
73+
}
74+
75+
return nil
76+
}
77+
78+
func getLatestRelease() (*githubRelease, error) {
79+
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", repoOwner, repoName)
80+
81+
client := &http.Client{Timeout: 10 * time.Second}
82+
resp, err := client.Get(url)
83+
if err != nil {
84+
return nil, err
85+
}
86+
defer resp.Body.Close()
87+
88+
if resp.StatusCode == 404 {
89+
return nil, fmt.Errorf("no releases found")
90+
}
91+
if resp.StatusCode != 200 {
92+
return nil, fmt.Errorf("GitHub API returned %d", resp.StatusCode)
93+
}
94+
95+
var release githubRelease
96+
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
97+
return nil, err
98+
}
99+
100+
return &release, nil
101+
}
102+
103+
func getAssetName() string {
104+
os := runtime.GOOS
105+
arch := runtime.GOARCH
106+
107+
ext := ".tar.gz"
108+
if os == "windows" {
109+
ext = ".zip"
110+
}
111+
112+
return fmt.Sprintf("claude-code-sync_%s_%s%s", os, arch, ext)
113+
}
114+
115+
// compareVersions returns >0 if a > b, <0 if a < b, 0 if equal
116+
func compareVersions(a, b string) int {
117+
aParts := strings.Split(a, ".")
118+
bParts := strings.Split(b, ".")
119+
120+
for i := 0; i < len(aParts) && i < len(bParts); i++ {
121+
var aNum, bNum int
122+
fmt.Sscanf(aParts[i], "%d", &aNum)
123+
fmt.Sscanf(bParts[i], "%d", &bNum)
124+
125+
if aNum > bNum {
126+
return 1
127+
}
128+
if aNum < bNum {
129+
return -1
130+
}
131+
}
132+
133+
return len(aParts) - len(bParts)
134+
}

0 commit comments

Comments
 (0)