Skip to content

feat: portable settings sync across machines via GitHub#40

Open
dipendave wants to merge 1 commit intoAlexPeppas:masterfrom
dipendave:feature/settings-sync
Open

feat: portable settings sync across machines via GitHub#40
dipendave wants to merge 1 commit intoAlexPeppas:masterfrom
dipendave:feature/settings-sync

Conversation

@dipendave
Copy link
Copy Markdown

Summary

Adds a complete settings sync system that keeps AgentPlex preferences and Claude CLI configuration in sync across machines using a private GitHub repo as the backend. Includes a test framework (vitest) with 56 tests, an expanded settings system, and a new Settings panel with a Monaco JSON editor.

Key features:

  • One-click GitHub sync setup — auto-creates a private agentplex-sync repo via gh CLI, with built-in GitHub login flow (supports GitHub.com and GHE)
  • Expanded settings system — all preferences stored in ~/.agentplex/settings.json, editable via a Monaco JSON editor in the Settings panel
  • Auto-sync — local file changes trigger debounced push (30s); lightweight GitHub API poll with ETags every 5 min for remote changes
  • Reactive preferences — font size, font family, theme (dark/light) apply immediately to the terminal and UI; Ctrl+/- persists to settings
  • Configurable sync scopesyncClaudeIncludes allowlist controls which ~/.claude/ files/dirs are synced (default: CLAUDE.md, settings.json, agents, commands, plugins)
  • Profile support (backend ready, UI hidden) — named sets of preferences stored as folders in the sync repo

What changed

New files

File Purpose
src/main/sync-engine.ts Git-based sync engine — clone, push, pull, disconnect, auto-sync, profiles, GitHub CLI helpers
src/main/settings-manager.test.ts 11 tests for expanded settings manager
src/main/sync-engine.test.ts 45 tests for sync engine (real git repos in temp dirs, no git mocking)
src/renderer/components/SettingsPanel.tsx Monaco JSON editor + sync controls + GitHub login flow
src/renderer/components/SyncConflictDialog.tsx Monaco diff editor for merge conflict resolution
vitest.config.mts Vitest test configuration

Modified files

File Changes
settings-manager.ts AppPreferences interface (extensible [key: string]: unknown), getAllSettings(), updateSettings(), invalidateCache()
ipc-channels.ts Re-exports sync types; adds SyncConflictFile, SyncConflictResolution; 17 new IPC channels
ipc-handlers.ts Handlers for sync setup/push/pull/disconnect/status, profiles CRUD, settings get/update, GitHub user/login
preload.ts 17 new bridge methods (sync, profiles, settings, event listeners)
types.ts Matching AgentPlexAPI interface additions
store.ts syncStatus, preferences state; PanelId includes 'settings'
ActivityBar.tsx Sync status icon at bottom with colored dot (green/spinning/yellow/red); theme toggle persists to settings
SidePanel.tsx Excludes settings panel (now rendered as overlay)
App.tsx Settings panel overlay, sync/settings event subscriptions on mount, auto-sync startup
main.ts Startup pull + auto-sync initialization
useTerminal.ts Reads font size/family from preferences; Ctrl+/- writes back to settings
package.json test/test:watch scripts, vitest dependency
CLAUDE.md Updated project structure, TDD guidelines, settings conventions, IPC pattern checklist

Architecture

Sync flow

Local edit → fs.watch → 30s debounce → copyLocalToSyncRepo → git commit → git push
Remote poll (5min) → gh api with ETag → 304? skip : git pull → applySyncRepoToLocal

Sync repo structure

agentplex-sync/          (private GitHub repo, auto-created)
  default/               (profile folder)
    agentplex-settings.json
    claude/
      CLAUDE.md
      settings.json
      agents/
      commands/
      plugins/

Settings preserved during sync

Sync-config fields (syncRepoUrl, syncLastSyncedAt, syncAutoSync, syncActiveProfile) are machine-specific and never overwritten by pulled settings.

Auto-sync safeguards

  • suppressWatcher flag prevents feedback loops during sync operations (2s cooldown)
  • localEditRef in the JSON editor prevents editor → save → broadcast → editor loops
  • syncing mutex prevents concurrent sync operations
  • Settings.json file watcher removed (was causing feedback loops); only ~/.claude/ dirs are watched

Test plan

  • 56 tests passing (pnpm test)
  • Zero TypeScript errors (npx tsc --noEmit)
  • Zero ESLint errors on production files
  • Manual: open Settings panel, verify JSON editor shows all settings including syncClaudeIncludes
  • Manual: click "Connect to GitHub", verify repo auto-created and initial push succeeds
  • Manual: edit a setting in JSON editor, verify terminal updates (e.g. fontSize)
  • Manual: toggle dark/light mode, verify it persists in settings.json
  • Manual: Ctrl+/- in terminal, verify Settings panel JSON updates
  • Manual: change a file in ~/.claude/commands/, wait 30s, verify push to GitHub
  • Manual: edit a file on GitHub, wait 5min (or click Sync Now), verify local update
  • Manual: click Disconnect, verify sync config removed

🤖 Generated with Claude Code

Add a complete settings sync system that keeps AgentPlex preferences and
Claude CLI configuration in sync across machines using a private GitHub
repo as the backend.

## Settings System

- Expand `settings-manager.ts` from a single `defaultShell` field to a
  full extensible `AppPreferences` with `getAllSettings()`,
  `updateSettings()`, and `invalidateCache()`
- All user-configurable values now live in `~/.agentplex/settings.json`
- Settings are edited via a Monaco JSON editor in the Settings panel
- Font size, font family, theme (dark/light), and all other preferences
  are reactive — changes apply immediately to the terminal and UI

## Sync Engine (`sync-engine.ts`)

- Git-based sync using a private GitHub repo (`agentplex-sync`)
- One-click setup: detects GitHub auth via `gh` CLI, auto-creates the
  repo if it doesn't exist, works with GitHub.com and GHE
- GitHub login flow built into the UI with device code display
- **Auto-sync**: `fs.watch` on `~/.claude/` dirs triggers debounced
  push (30s); lightweight GitHub API poll with ETags every 5 min for
  remote changes (304 Not Modified = zero cost)
- Conflict resolution via Monaco diff editor (`SyncConflictDialog.tsx`)
- Profile support in the backend (folders in sync repo: `default/`,
  `work/`, etc.) with create/switch/rename/delete — UI hidden for now
- Automatic migration from flat repo layout to profile-based folders
- `syncClaudeIncludes` setting controls what gets synced from
  `~/.claude/` (allowlist: CLAUDE.md, settings.json, agents, commands,
  plugins by default)
- Sync config fields preserved during pull (machine-specific fields
  like `syncRepoUrl` are never overwritten by synced settings)

## UI

- Settings panel opens as a floating overlay (no layout shift)
- Sync status icon at bottom of ActivityBar with colored dot indicator:
  green (synced), spinning (syncing), yellow (conflict), red (error)
- Sync controls: "Sync Now" button + "Disconnect"
- Tooltip on Settings Sync header explaining what gets synced
- Terminal font size/family reactive to preferences — Ctrl+/- also
  persists to settings.json for sync
- Theme toggle now persists to settings.json for cross-machine sync

## Testing

- Set up vitest with `pnpm test` / `pnpm test:watch`
- 56 tests across 2 test files:
  - `settings-manager.test.ts` (11): load, save, merge, cache,
    invalidate, extensibility, backward compat, sync config fields
  - `sync-engine.test.ts` (45): file walking with allowlist, 1MB
    guard, copy to/from repo, setup (empty/existing), push, pull,
    disconnect, status, auto-sync, GitHub user parsing (GH + GHE),
    profiles (list/create/switch/rename/delete/protection), custom
    syncClaudeIncludes, flat-to-profile migration, sync config
    preservation
- Tests use real git repos in temp directories (no git mocking)
- CLAUDE.md updated with TDD guidelines and settings conventions

## New Files

- `src/main/sync-engine.ts` — sync engine
- `src/main/settings-manager.test.ts` — settings tests
- `src/main/sync-engine.test.ts` — sync tests
- `src/renderer/components/SettingsPanel.tsx` — JSON editor + sync UI
- `src/renderer/components/SyncConflictDialog.tsx` — conflict resolver
- `vitest.config.mts` — test configuration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants