Skip to content

Commit a190eae

Browse files
FelixIsaacclaude
andcommitted
Rewrite in Go with native age encryption
- Single binary, no external age CLI needed - Cross-platform (Win/Mac/Linux, amd64/arm64) - Native filippo.io/age encryption - --dry-run flag for push/pull - doctor command for setup verification - goreleaser + GitHub Actions for releases 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4a0ff0b commit a190eae

23 files changed

Lines changed: 2092 additions & 36 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build:
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
matrix:
14+
os: [ubuntu-latest, macos-latest, windows-latest]
15+
go: ['1.21']
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Set up Go
21+
uses: actions/setup-go@v5
22+
with:
23+
go-version: ${{ matrix.go }}
24+
25+
- name: Build
26+
run: go build -v ./...
27+
28+
- name: Test
29+
run: go test -v ./...
30+
31+
lint:
32+
runs-on: ubuntu-latest
33+
steps:
34+
- uses: actions/checkout@v4
35+
36+
- name: Set up Go
37+
uses: actions/setup-go@v5
38+
with:
39+
go-version: '1.21'
40+
41+
- name: golangci-lint
42+
uses: golangci/golangci-lint-action@v4
43+
with:
44+
version: latest

.github/workflows/release.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
goreleaser:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
20+
- name: Set up Go
21+
uses: actions/setup-go@v5
22+
with:
23+
go-version: '1.21'
24+
25+
- name: Run GoReleaser
26+
uses: goreleaser/goreleaser-action@v6
27+
with:
28+
distribution: goreleaser
29+
version: latest
30+
args: release --clean
31+
env:
32+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Binary (root only)
2+
/claude-code-sync
3+
/claude-code-sync.exe
4+
/dist/
5+
6+
# IDE
7+
.idea/
8+
.vscode/
9+
*.swp
10+
*.swo
11+
12+
# OS
13+
.DS_Store
14+
Thumbs.db

.goreleaser.yaml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
version: 2
2+
3+
project_name: claude-code-sync
4+
5+
before:
6+
hooks:
7+
- go mod tidy
8+
9+
builds:
10+
- id: claude-code-sync
11+
main: ./cmd/claude-code-sync
12+
binary: claude-code-sync
13+
env:
14+
- CGO_ENABLED=0
15+
goos:
16+
- linux
17+
- darwin
18+
- windows
19+
goarch:
20+
- amd64
21+
- arm64
22+
ldflags:
23+
- -s -w -X main.version={{.Version}}
24+
25+
archives:
26+
- id: default
27+
format: tar.gz
28+
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
29+
format_overrides:
30+
- goos: windows
31+
format: zip
32+
files:
33+
- README.md
34+
- LICENSE
35+
36+
checksum:
37+
name_template: 'checksums.txt'
38+
39+
changelog:
40+
sort: asc
41+
filters:
42+
exclude:
43+
- '^docs:'
44+
- '^test:'
45+
- '^chore:'
46+
- Merge pull request
47+
- Merge branch
48+
49+
brews:
50+
- repository:
51+
owner: felixisaac
52+
name: homebrew-tap
53+
directory: Formula
54+
homepage: https://github.com/felixisaac/claude-code-sync
55+
description: Sync Claude Code configs across machines via GitHub with age encryption
56+
license: MIT
57+
install: |
58+
bin.install "claude-code-sync"
59+
test: |
60+
system "#{bin}/claude-code-sync", "version"
61+
62+
scoops:
63+
- repository:
64+
owner: felixisaac
65+
name: scoop-bucket
66+
homepage: https://github.com/felixisaac/claude-code-sync
67+
description: Sync Claude Code configs across machines via GitHub with age encryption
68+
license: MIT
69+
70+
release:
71+
github:
72+
owner: felixisaac
73+
name: claude-code-sync
74+
draft: false
75+
prerelease: auto

README.md

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,48 @@ Secure cross-platform CLI to sync [Claude Code](https://claude.com/claude-code)
55
## Why?
66

77
- **Multi-machine developers**: Keep your custom commands, agents, skills, and settings in sync across desktop, laptop, and SSH servers
8-
- **Security**: API keys and OAuth tokens are encrypted with age before pushing to GitHub
8+
- **Security**: API keys and OAuth tokens are encrypted with native age before pushing to GitHub
99
- **Simple**: Just `push` and `pull` - no learning curve, no complex setup
10-
- **Open source**: Audit the code yourself - it's ~400 lines of bash/PowerShell
10+
- **Single binary**: No external dependencies except git - age encryption is built-in
1111

1212
## Features
1313

1414
- Selective encryption (plain text for non-sensitive files, encrypted for secrets)
15-
- Cross-platform (bash for macOS/Linux, PowerShell for Windows)
15+
- Cross-platform single binary (Windows, macOS, Linux - amd64/arm64)
16+
- Native age encryption built-in (no external age CLI needed)
17+
- `--dry-run` flag for push and pull
1618
- Automatic backups before pull
1719
- Conflict detection with local backup
1820
- SHA256 integrity verification
21+
- `doctor` command to verify setup
1922

2023
## Prerequisites
2124

22-
### age (encryption tool)
23-
24-
**[age](https://github.com/FiloSottile/age)** is a simple, modern file encryption tool. claude-code-sync uses it to encrypt your sensitive files (API keys, OAuth tokens) before pushing to GitHub.
25-
26-
```
27-
settings.json (contains secrets) → age encrypts → settings.json.age (unreadable) → GitHub
28-
```
29-
30-
**Install age:**
31-
32-
| Platform | Command |
33-
|----------|---------|
34-
| macOS | `brew install age` |
35-
| Ubuntu/Debian | `sudo apt install age` |
36-
| Windows (scoop) | `scoop bucket add extras && scoop install age` |
37-
| Windows (winget) | `winget install FiloSottile.age` |
38-
| Manual | [Download from releases](https://github.com/FiloSottile/age/releases) |
39-
4025
### git
4126

42-
You'll also need `git` installed (you probably already have it).
27+
You'll need `git` installed (you probably already have it).
4328

44-
## Installation
29+
**Note**: age encryption is now built into the binary - no external age CLI needed!
4530

46-
The install script downloads `claude-code-sync` to `~/.local/bin/` and adds it to your PATH.
31+
## Installation
4732

48-
**macOS/Linux:**
33+
**macOS (Homebrew):**
4934
```bash
50-
curl -fsSL https://raw.githubusercontent.com/felixisaac/claude-code-sync/main/install.sh | bash
35+
brew install felixisaac/tap/claude-code-sync
5136
```
5237

53-
**Windows (PowerShell):**
38+
**Windows (Scoop):**
5439
```powershell
55-
irm https://raw.githubusercontent.com/felixisaac/claude-code-sync/main/install.ps1 | iex
40+
scoop bucket add felixisaac https://github.com/felixisaac/scoop-bucket
41+
scoop install claude-code-sync
42+
```
43+
44+
**Go:**
45+
```bash
46+
go install github.com/felixisaac/claude-code-sync@latest
5647
```
5748

58-
**Manual installation:** You can also just download `claude-code-sync` (Unix) or `claude-code-sync.ps1` (Windows) directly and put it somewhere in your PATH.
49+
**Manual:** Download the latest binary from [GitHub Releases](https://github.com/felixisaac/claude-code-sync/releases).
5950

6051
### First Time Setup
6152

@@ -94,17 +85,16 @@ claude-code-sync pull
9485
| Command | Description |
9586
|---------|-------------|
9687
| `init [repo-url]` | Initialize sync (generate keys, clone/create repo) |
97-
| `push` | Encrypt and push configs to GitHub |
98-
| `pull` | Pull and decrypt configs from GitHub |
88+
| `push [--dry-run]` | Encrypt and push configs to GitHub |
89+
| `pull [--dry-run]` | Pull and decrypt configs from GitHub |
9990
| `status` | Show sync status |
91+
| `doctor` | Check system health and setup |
10092
| `import-key` | Import private key on new machine |
10193
| `export-key` | Display private key for backup |
10294
| `verify` | Verify file integrity |
103-
| `reset` | Delete all sync data (⚠️ deletes key!) |
104-
| `reset --keep-key` | Reset but preserve your private key |
95+
| `reset [--keep-key]` | Delete all sync data |
10596
| `unlink` | Disconnect from remote repo (keep local data) |
106-
| `check-update` | Check if a new version is available |
107-
| `update` | Download and install latest version |
97+
| `version` | Show version |
10898
| `help` | Show help |
10999

110100
## What Gets Synced
@@ -143,7 +133,7 @@ claude-code-sync pull
143133
## Security
144134

145135
### Encryption
146-
- Uses [age](https://github.com/FiloSottile/age) (modern, audited encryption)
136+
- Uses [filippo.io/age](https://github.com/FiloSottile/age) Go library (built-in, no external CLI)
147137
- X25519 key exchange
148138
- ChaCha20-Poly1305 encryption
149139

cmd/claude-code-sync/main.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package main
2+
3+
import (
4+
"os"
5+
6+
"github.com/felixisaac/claude-code-sync/internal/cmd"
7+
)
8+
9+
// Set by goreleaser ldflags
10+
var version = "dev"
11+
12+
func main() {
13+
cmd.SetVersion(version)
14+
if err := cmd.Execute(); err != nil {
15+
os.Exit(1)
16+
}
17+
}

go.mod

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module github.com/felixisaac/claude-code-sync
2+
3+
go 1.24.5
4+
5+
require (
6+
filippo.io/age v1.2.1
7+
github.com/fatih/color v1.18.0
8+
github.com/spf13/cobra v1.10.2
9+
gopkg.in/yaml.v3 v3.0.1
10+
)
11+
12+
require (
13+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
14+
github.com/mattn/go-colorable v0.1.13 // indirect
15+
github.com/mattn/go-isatty v0.0.20 // indirect
16+
github.com/spf13/pflag v1.0.9 // indirect
17+
golang.org/x/crypto v0.24.0 // indirect
18+
golang.org/x/sys v0.25.0 // indirect
19+
)

go.sum

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
2+
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
3+
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
4+
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
5+
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
6+
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
7+
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
8+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
9+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
10+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
11+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
12+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
13+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
14+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
15+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
16+
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
17+
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
18+
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
19+
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
20+
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
21+
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
22+
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
23+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
24+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
25+
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
26+
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
27+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
28+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
29+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
30+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)