feat(multi-account): cross-account sharing — engine + CLI + UI (Batch 3)

Merged
GR
4mo ago
+1303-11

Summary

Batch 3: cross-account sharing — share the anchor account's global files (CLAUDE.md / skills/ / commands/) with other accounts, per item: Link (symlink, stays in sync) / Copy (independent fork) / skip. Engine + CLI + UI in one PR (per discussion, so it's testable end-to-end from the UI).

Depends on #129 (the isAnchor refactor — merged). Sharing mechanism was verified live earlier (Claude Code follows symlinks for skills entries, whole dirs, and CLAUDE.md; this machine has been running on hand-made links since).

Semantics (the part worth reviewing carefully)

  • Never silently overwrite: sharing over an account's own real file/dir first renames it to a timestamped .codev-bak-* sibling (never ln -sf). An existing symlink is just replaced (a pointer needs no backup).
  • Unshare is reversible by construction:
    • plain Unlink — zero data loss (removes a pointer; the anchor's copy is untouched; re-link anytime)
    • Unlink & restore (--restore-backup) — true undo: brings back the content the account had before sharing
    • --keep-copy — materializes the shared content as an independent fork
  • Status engine distinguishes none / own / linked → target / broken-link / mixed (a real dir containing per-entry links) — and correctly recognizes pre-existing hand-made links it didn't create.
  • sync-settings copies statusLine / model / effortLevel / theme from the anchor (settings.json keys). Hooks (installed per-dir by CodeV), enabledPlugins, and permissions are deliberately excluded.
  • plugins/ is NOT shareable — experiment result: it's a per-account install registry (installed_plugins.json with absolute installPaths) + caches, i.e. mutable state, not payload. Same exclusion class as session data.

What's included

  • src/cli/share-manager.ts — path-based engine (tmpdir-testable) + registry-aware wrappers
  • CLI: codev account share <name> (status), share <name> <item> --link|--copy [--entry E], unshare <name> <item> [--restore-backup|--keep-copy], sync-settings <name> <key...>; tab completion for all of it
  • UI: Sharing button per non-anchor account row → inline panel (per-item status + Link / Copy / Unlink / Unlink & restore; mixed dirs point at the CLI's --entry)
  • IPC trio (accounts-share-status/share/unshare) as thin wrappers
  • 12 real-fs integration tests under os.tmpdir() (32 total)

Also added during review/testing

  • Settings-key sync in the UI (user request): the Sharing panel gains a settings row with one-click buttons — statusLine (the headline case) / model / effortLevel / theme — each copies that key from the anchor with copied/missing feedback.
  • Panel freshness (user-reported: stale state persisted until an app restart): share status reloads when the Accounts tab reopens and when the window regains focus (the natural flow after editing files in a terminal), gated to fire only while the panel is visible.
  • Sharing ▸/▾ disclosure arrow (user feedback: the button read as a one-shot action, not an expander).
  • Hardening from the review rounds: unshare validates the restore-backup/keep-copy source before unlinking (a failed undo can't half-remove the share); keep-copy stages to a unique temp sibling first (no TOCTOU window, no stale-temp contamination); backup names uniquified beyond 1s timestamp precision; CLI rejects ambiguous --link --copy and the IPC validates the mode; entry names starting with - fail early; malformed settings.json errors include the path; popup ignores stale share-status responses when rapidly switching accounts.
  • Deferred with rationale (on the threads): renderer tilde helper stays regex-based (macOS-only app; no os in the renderer); typing preload against IElectronAPI needs the d.ts to become a module first — its own tiny refactor.

How to test (UI, needs yarn make)

  1. Settings → Accounts → Sharing on the work row → panel should show your existing hand-made links: all three items linked → ~/.claude/... (the engine recognizing state it didn't create).
  2. Unlink commands → status none; check ~/.claude/commands still exists (source untouched). Link it back → linked again.
  3. Fork test: Unlink an item then Copy it → status own (not shared); editing the copy won't touch the anchor's version. (Restore to Link afterwards.)
  4. Backup/undo test: echo test > ~/.claude-work/CLAUDE.md after unlinking claude-md → panel shows own → Link (notice mentions backup) → Unlink & restore → your test content is back.
  5. CLI spot-check: yarn account share work (status table), yarn account sync-settings work model (copies your anchor's model setting), claude w<TAB> still completes.
  6. Settings row: in the Sharing panel click statusLine → notice Copied statusLine from the anchor….
  7. Freshness: with the panel open, change a target from a terminal (e.g. rm/echo on ~/.claude-work/CLAUDE.md) → cmd-tab back to CodeV → the row updates without restarting.

tsc --noEmit 0 errors; yarn test 35/35 (14 real-fs engine tests).

Notes

  • Per-entry sharing (--entry <skill-name>) is CLI-only in this PR; the UI shows mixed state and defers to the CLI. UI per-entry management can come later if wanted.
  • Backlog after this: #127 (detect existing config dirs + one-click register), #128 (copy-fork resume), #121 (VS Code).

🤖 Generated with Claude Code


Summary by cubic

Adds cross-account sharing and one-click settings sync so non-anchor accounts can reuse the anchor’s global CLAUDE.md, skills/, and commands/. Includes the engine, CLI, UI, and docs updates for end-to-end testing; version 1.0.82.

  • New Features

    • Path-based share engine with clear status: none / own / linked → target / broken-link / mixed (recognizes hand-made links).
    • Safe by default: never overwrite; own content backed up to .codev-bak-<ts>; Unlink is lossless; Unlink & restore truly undoes; --keep-copy materializes a fork via a unique temp.
    • CLI: codev account share <name> (status), share <name> <item> --link|--copy [--entry E], unshare <name> <item> [--entry E] [--restore-backup|--keep-copy], sync-settings <name> <key...>; completion for labels/items/keys.
    • UI: per-account Sharing panel with per-item status and actions (Link/Copy/Unlink/Unlink & restore), plus per-key sync buttons for statusLine, model, effortLevel, theme; panel refreshes on Accounts tab reopen and when the window regains focus; mixed dirs point to the CLI.
    • plugins/ not shareable (per-account install state). IPC endpoints for share status/share/unshare/sync-settings; UI renders syncable settings keys from the engine’s IPC allowlist. Real-fs integration tests cover the engine. README/design docs updated.
  • Bug Fixes

    • Panel freshness: reload on reopen/focus; stale share-status responses ignored during rapid account switches.
    • Unshare validates restore-backup/keep-copy before unlinking; backup names uniquified; --keep-copy copies to a unique temp before unlinking (no TOCTOU).
    • CLI/IPC validation: reject ambiguous --link --copy; validate mode; early-reject --entry names starting with -; settings JSON errors show the path; accounts-sync-settings validates keys is an array.

Summary by CodeRabbit

  • New Features
    • Added cross-account sharing for Claude settings, skills, and commands (link for sync or copy for independence).
    • Added per-item Sharing controls in the Accounts view, including link/copy, unlink, and optional “unlink & restore” using recoverable backups.
    • Extended the CLI with codev account subcommands: share, unshare, and sync-settings (syncs an allowlisted set of settings keys).
  • Bug Fixes
    • Improved protection against overwriting by creating recoverable backups and supporting restore/keep-copy outcomes.
  • Documentation
    • Updated the changelog and multi-account support documentation with the new UI/CLI behavior.
  • Chores
    • Released version 1.0.82.