npm package: @slvnlrt/opencode-bridge
Description: Sync OpenCode config/sessions to GitHub + bidirectional bridge with Claude Code
Latest version: 0.2.6

# opencode-bridge

Sync [OpenCode](https://opencode.ai) config and sessions across machines via GitHub, with bidirectional [Claude Code](https://docs.anthropic.com/en/docs/claude-code) session conversion.

```bash
npm install -g @slvnlrt/opencode-bridge
```

---

## What it does

**opencode-bridge** syncs your OpenCode config, sessions, and Claude Code JSONL files through a private GitHub repo. One command does everything:

```bash
opencode-bridge sync
```

This runs the full pipeline: **backup -> pull -> resolve conflicts -> export -> import -> push**. Config, sessions, and Claude Code JSONL files all travel through a single private GitHub repo. Every push is a squash orphan commit -- the repo never grows beyond one commit, which prevents Git LFS quota exhaustion.

Running `opencode-bridge` with no arguments defaults to `sync`.

## Quick Start

### 1. Install

One command:

```bash
npm install -g github:slvnlrt/opencode-bridge
```

Or from source:

```bash
git clone https://github.com/slvnlrt/opencode-bridge.git
cd opencode-bridge
npm install && npm run build && npm link
```

### 2. Set your remote

```bash
# macOS/Linux -- add to ~/.zshrc or ~/.bashrc
export SYNC_REMOTE_URL="https://github.com/YOUR_USER/YOUR_PRIVATE_REPO.git"

# Windows -- run once in PowerShell
# [System.Environment]::SetEnvironmentVariable("SYNC_REMOTE_URL", "https://github.com/YOUR_USER/YOUR_PRIVATE_REPO.git", "User")
```

### 3. Sync

```bash
opencode-bridge sync          # Full sync (default)
opencode-bridge sync --dry-run  # Preview without changes
```

## CLI Reference

### Primary Command: `sync`

Full sync: backup -> pull -> resolve conflicts -> export -> import -> push.

| Flag | Description |
|---|---|
| `--import-only` | Only import (backup -> pull -> import) |
| `--export-only` | Only export (backup -> pull -> export -> push) |
| `--no-push` | Skip the final push to GitHub |
| `--no-pull` | Skip pulling from remote |
| `--dry-run` | Show what would happen without making changes |
| `-f, --force` | Skip conflict prompts, use newer-wins |
| `--auto-rollback` | Auto-restore database backup on failure |

Running with no arguments defaults to `sync`.

### Bridge Commands

| Command | Description |
|---|---|
| `import-from-claude` | Import Claude Code sessions into OpenCode |
| `export-to-claude` | Export OpenCode sessions to Claude Code |
| `status` | Show sessions in each tool and their sync state |
| `watch` | Watch for changes and sync automatically |

**Options for `import-from-claude` / `export-to-claude`:**

| Flag | Description |
|---|---|
| `-p, --project <path>` | Source project path (default: all) |
| `-s, --session <id>` | Specific session ID |
| `--since <date>` | Only sync sessions modified after this date |
| `--dry-run` | Show what would be synced without writing |

**Options for `watch`:**

| Flag | Description |
|---|---|
| `-d, --direction <dir>` | Sync direction: `both`, `to-claude`, `from-claude` (default: `both`) |

### GitHub Aliases (legacy)

These are convenience aliases that route through `sync`:

| Command | Description |
|---|---|
| `push` | Push config/sessions to GitHub (alias for `sync --export-only`) |
| `pull` | Pull config/sessions from GitHub (alias for `sync --import-only --no-push`) |
| `github-status` | Show GitHub sync status for config and sessions |

**Options for `push` / `pull`:**

| Flag | Description |
|---|---|
| `-t, --target <target>` | What to sync: `config`, `sessions`, `both` (default: `both`) |
| `-f, --force` | Force push/pull (override conflicts or discard local changes) |

## How It Works

<p align="center">
  <img src="docs/architecture.svg" alt="Architecture Diagram" width="700"/>
</p>

<p align="center">
  <img src="docs/demo.svg" alt="Demo" width="600"/>
</p>

### Sync Mapping

Local directories map to paths inside the sync repo:

| Local Path | Repo Path |
|---|---|
| `~/.config/opencode/` | repo root |
| `~/.local/share/opencode/` | `_data/` |
| `~/.local/state/opencode/` | `_state/` |
| `~/.agents/skills/` | `_agents/` |
| Claude Code JSONL sessions | `_claude/` |

### Squash Push

Every push creates a single **orphan commit** -- the repo always contains exactly one commit. This means:

- Git LFS only ever stores one copy of the SQLite database
- No history growth, no accumulating LFS objects
- Prevents the LFS quota exhaustion that occurs with normal incremental commits

### JSONL Backups

Before each push, Claude Code session files (`_claude/`) are archived to `_backups/` as a `.tar.gz` file. The 5 most recent archives are retained; older ones are automatically pruned.

### SQLite Safety

- Database backup via the SQLite `.backup` command (safe with concurrent readers/writers in WAL mode)
- Pull merges remote sessions into the local DB instead of replacing it -- no data loss
- Integrity verification after pull
- Migration record repair when pulling an older database version
- Sync runs while OpenCode is open -- no need to close it

### Git LFS

The SQLite database is tracked via Git LFS. After each push, `git lfs prune` runs to clean the local LFS cache.

## Prerequisites

- **Node.js** v20+
- **Git** + [Git LFS](https://git-lfs.github.com/)
- **SQLite3 CLI** -- for database backup and integrity checks
- **A private GitHub repository** -- create one, keep it empty

## Environment Variables

| Variable | Required | Description |
|---|---|---|
| `SYNC_REMOTE_URL` | **Yes** | Your private GitHub repo URL |
| `SYNC_CONFIG_ROOT` | No | Override config dir (default: `~/.config/opencode`) |
| `SYNC_DATA_ROOT` | No | Override data dir (default: `~/.local/share/opencode`) |
| `SYNC_STATE_ROOT` | No | Override state dir (default: `~/.local/state/opencode`) |
| `SYNC_AGENTS_ROOT` | No | Override agents dir (default: `~/.agents/skills`) |
| `SYNC_SKIP_LFS` | No | Set to `1` to skip Git LFS |

## Troubleshooting

**LFS quota exceeded**
Delete and recreate the remote repository. The squash push strategy prevents future quota growth since only one commit (and one LFS object) ever exists.

**"OpenCode processes still running"**
This message no longer appears -- sync now works while OpenCode is running. If you see it in an older version, upgrade to the latest build.

**Stash pop failed**
Resolve manually:
```bash
cd ~/.config/opencode && git stash pop
```

**Database integrity check failed**
Restore from a recent backup:
```bash
cp ~/.local/share/opencode-bridge/backups/<latest>.db ~/.local/share/opencode/opencode.db
```

**Push failed (force-with-lease rejected)**
Another machine pushed since your last pull. Pull first:
```bash
opencode-bridge pull --force
opencode-bridge sync
```

## New to This?

If you are not familiar with Git, GitHub CLI, or environment variables, paste this prompt into OpenCode and let it handle the setup:

```
I want to sync my OpenCode config and sessions across multiple machines.
Help me set up everything from scratch:

1. Check if Git, Git LFS, Node.js, and SQLite3 are installed -- install anything missing
2. Check if gh (GitHub CLI) is installed -- if not, install it, then help me log in to GitHub
3. Create a private GitHub repo for storing my sync data
4. Clone the opencode-bridge project, run npm install && npm run build && npm link
5. Set the SYNC_REMOTE_URL environment variable for me
6. Run "opencode-bridge sync --dry-run" to verify everything works
```

## Project Structure

```
opencode-bridge/
├── src/
│   ├── cli.ts                          # CLI entry point (commander)
│   ├── config.ts                       # Shared configuration
│   │
│   ├── commands/                       # CLI command handlers
│   │   ├── sync.ts                    #   sync (primary command)
│   │   ├── github-sync.ts            #   github-status
│   │   ├── import.ts                  #   import-from-claude
│   │   ├── export.ts                  #   export-to-claude
│   │   ├── status.ts                  #   status
│   │   └── watch.ts                   #   watch
│   │
│   ├── github/                         # GitHub sync engine
│   │   ├── git.ts                     #   Git CLI wrapper + helpers
│   │   ├── squash.ts                  #   Orphan-commit squash push
│   │   ├── fs-utils.ts               #   File system utilities
│   │   ├── paths.ts                   #   XDG path resolution
│   │   ├── process.ts                #   OpenCode process detection/kill
│   │   ├── sqlite-ops.ts             #   SQLite backup, verify, migration repair
│   │   ├── sync-config.ts            #   Config staging/pull logic
│   │   └── sync-sessions.ts          #   Session staging/pull logic
│   │
│   ├── adapters/                       # Data adapters for bridge
│   │   ├── claude/                    #   Claude Code JSONL reader/writer
│   │   └── opencode/                  #   OpenCode SQLite reader/writer
│   │
│   ├── converter/                      # Session format converters
│   │   ├── claude-to-opencode.ts      #   JSONL -> SQLite
│   │   ├── opencode-to-claude.ts      #   SQLite -> JSONL
│   │   └── id-map.ts                  #   Deterministic ID mapping
│   │
│   └── sync/                           # Sync state and orchestration
│       ├── engine.ts                  #   Import/export orchestration
│       ├── state.ts                   #   Sync state tracking
│       ├── conflict.ts               #   Conflict detection and resolution
│       ├── merge.ts                   #   Session-level DB merge (latest action wins)
│       ├── backup.ts                  #   Database + JSONL backup/archive
│       ├── jsonl-sync.ts             #   JSONL file staging/restore
│       └── prompt.ts                  #   Interactive prompts
│
├── tests/
│   ├── converter.test.ts              # Converter unit tests
│   └── github-utils.test.ts           # GitHub sync utility tests
│
├── docs/                               # SVG diagrams
├── package.json
├── tsconfig.json
├── tsup.config.ts
└── README.md
```

**Sync repo structure** (what your private GitHub repo looks like):

```
your-sync-repo/
├── config.json, skills/, ...          # OpenCode config (repo root)
├── _data/                             # OpenCode data (auth, db)
├── _state/                            # OpenCode state (frecency, kv)
├── _agents/                           # Skills CLI installs
├── _claude/                           # Claude Code JSONL sessions
└── _backups/                          # Archived JSONL snapshots (5 retained)
```

## Development

```bash
npm install           # Install dependencies
npm run build         # Build to dist/
npm run dev           # Build + watch
npm test              # Run tests (vitest)
npm run typecheck     # TypeScript type check
```

### Publishing a new version

```bash
npm version patch     # Bump version (0.2.2 → 0.2.3)
npm publish --access public
git push origin main --tags
```

Users update with: `npm install -g @slvnlrt/opencode-bridge`

## License

[MIT](./LICENSE)