mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
Agent platforms read hook config only from the directory a session starts in — never a parent, never a subfolder. Project-level hooks therefore fired only for sessions that happened to start at the mount, and, living inside a synced folder, they replicated one machine's agent config to the whole team (a second writer of a file bdrive already owns). Claude Code additionally ignores project hooks until the folder is trusted, so in practice they were often inert without any visible sign. Hooks now go to each platform's user config, once per machine, covering every session in every folder; the existing shell guard keeps them a no-op outside BearDrive projects. Install migrates away blocks older versions wrote into projects, and `bdrive hooks uninstall` removes ours while leaving foreign hooks untouched. Setup is also one command now. init absorbs the skill install, prints the hub link, and takes --server, so connecting to a named hub no longer needs a separate login; the runbook forbids preflight and command chaining, since each distinct command costs the user a permission prompt. For plugin users a PreToolUse hook auto-approves bdrive's own setup subcommands — narrowly: any shell operator in the command disqualifies it. Also drops --shared in favor of `init . --only wiki,docs`, which writes a managed block of .bdriveignore rules instead of a second scope mechanism. Because those rules sync, `sync --prune` now refuses on a scoped project rather than stripping everything outside the scope from the hub for everyone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016aYntCWwdUhpzUfEk3ddyJ
174 lines
6.7 KiB
Markdown
174 lines
6.7 KiB
Markdown
# `bdrive` CLI & sync engine — class diagram
|
|
|
|
Source of truth: `cmd/bdrive` (commands, gates) and `internal/{syncer,store,
|
|
journal,config,daemon,agenthooks,agentskills}`; the `internal/remote` seam is drawn in
|
|
[webapp-server.md](webapp-server.md). Reflects the code as of this commit;
|
|
update this file in any PR that changes these types or their relationships.
|
|
|
|
## Sync engine — one cycle
|
|
|
|
```mermaid
|
|
classDiagram
|
|
direction LR
|
|
|
|
class Session {
|
|
+Folder string
|
|
+MountID string
|
|
+Store *store.Store
|
|
+Device config.Device
|
|
+Account config.Settings
|
|
+Backend remote.Backend
|
|
+Note string
|
|
+Prune bool
|
|
+OnProgress func
|
|
+Cycle(ctx) Result
|
|
}
|
|
note for Session "internal/syncer — scan → commit local ops → pull peer journals → preserve conflicts → refresh rules → prune → materialize → push blobs then own journal"
|
|
note for Session "Prune (bdrive forget / sync --prune, never the daemon) journals a delete for every replayed path the SHARED ignore rules exclude — the include scope is per-device and must never prune it"
|
|
|
|
class Result {
|
|
+LocalOps +PulledOps
|
|
+Conflicts +Pruned +Materialized
|
|
+Pushed +Offline +OfflineErr
|
|
+ReadOnly +NoAccess +AccessErr
|
|
}
|
|
note for Result "Offline / ReadOnly / NoAccess are three different answers: unreachable (retry all), push refused (pull-only), pull refused (pause, touch nothing)"
|
|
|
|
class Filter {
|
|
+Skip(rel) bool
|
|
+PruneDir(rel) bool
|
|
}
|
|
note for Filter "ignore.go — .bdriveignore rules (incl. the managed `# bdrive scope` negation block written by init --only / bdrive scope) + a legacy .bdrive include list, applied symmetrically in scan and materialize; Negated() is what makes sync --prune refuse on a scoped project"
|
|
|
|
class Store {
|
|
-dir volume dir
|
|
+PutBlob / OpenBlob / HasBlob
|
|
+AppendOps / DeviceOps / AllOps
|
|
+LoadCache / SaveCache mountID
|
|
+LoadSync / SaveSync
|
|
+SaveNote / LoadNote
|
|
+PendingReads read spool
|
|
+Lock() flock
|
|
}
|
|
note for Store "internal/store — ~/.bdrive/volumes/mount-id: content-addressed blobs, per-device journal copies, state cache, paused marker (free funcs Paused/SetPaused, no flock)"
|
|
|
|
class Op {
|
|
+Seq +Lamport +Time +Device
|
|
+Author +User +UserName
|
|
+Kind put or delete
|
|
+Path +Blob +Size +Mode +Note
|
|
}
|
|
note for Op "internal/journal — Less orders by (lamport, time, device, seq); Replay folds to LWW-per-path state; each device writes only its own journal"
|
|
|
|
class Backend {
|
|
<<interface>>
|
|
+Put +Get +List +Exists +Close
|
|
+ErrForbidden sentinel
|
|
}
|
|
note for Backend "internal/remote — client devices use the https:// hub backend (token from BDRIVE_TOKEN / settings.json); a hub 403 wraps ErrForbidden, which is what Result turns into ReadOnly/NoAccess instead of Offline"
|
|
|
|
class daemon {
|
|
+Run(folder, scan, remote)
|
|
+Start / Stop / Running
|
|
}
|
|
note for daemon "per-mount detached loop; re-reads .bdrive/config.json each tick, exits without deletes if it vanishes"
|
|
|
|
Session --> Store : volume state
|
|
Session --> Backend : pull and push
|
|
Session --> Filter : scan and materialize
|
|
Session ..> Op : commits, replays
|
|
Session --> Result
|
|
Store o-- Op : journal files
|
|
daemon --> Session : one Cycle per tick
|
|
```
|
|
|
|
## CLI commands, device state, and the opt-in gate
|
|
|
|
```mermaid
|
|
classDiagram
|
|
direction LR
|
|
|
|
class Commands {
|
|
init login logout
|
|
sync stop scope forget status log
|
|
url share export import
|
|
web daemon hooks read-log skill
|
|
hook-approve PreToolUse
|
|
}
|
|
note for Commands "cmd/bdrive — thin cobra layer; init is the front door (one command: login + skill + hooks + sync + link), stop pauses; hook-approve auto-approves only bdrive's own setup subcommands for the plugin's PreToolUse hook"
|
|
|
|
class syncBlocked {
|
|
<<gate>>
|
|
enrolled in mounts.json?
|
|
volume not paused?
|
|
}
|
|
note for syncBlocked "cmd/bdrive/helpers.go — sync, sync --hook, and read-log must pass it; reads the registry WITHOUT ResolveMount's enrolling self-heal. Hook mode fails silent; plain sync errors with a bdrive init pointer"
|
|
|
|
class openSession {
|
|
mustProject → ResolveMount
|
|
store.Open + remote.Open
|
|
}
|
|
class startSync {
|
|
enroll + clear paused
|
|
initial Cycle
|
|
daemon.Start
|
|
}
|
|
note for startSync "cmd/bdrive/sync_run.go — init's engine; the ONLY enroller and the only thing that resumes a pause"
|
|
|
|
class Project {
|
|
+ID stable mount id
|
|
+Volume +Remote
|
|
+Include legacy, read-only
|
|
}
|
|
note for Project ".bdrive/config.json — travels with the folder (git clone, copy); presence alone is NOT consent to sync"
|
|
|
|
class MountRegistry {
|
|
mounts.json
|
|
id → Path Volume Remote
|
|
}
|
|
class Device {
|
|
device.json
|
|
}
|
|
class Settings {
|
|
settings.json
|
|
server + token + account
|
|
}
|
|
note for MountRegistry "internal/config — per-device state under BDRIVE_HOME; ResolveMount self-heals the path for enrolled mounts (renames/moves stay free)"
|
|
|
|
class AgentHooks {
|
|
Detect / Install / Uninstall / Registered
|
|
ConfigPath = USER config
|
|
turn-start: sync --hook
|
|
post-edit: sync --note
|
|
post-read: read-log
|
|
}
|
|
note for AgentHooks "internal/agenthooks — registers per-platform hook commands (claude, codex, gemini, hermes) in each platform's USER config, once per machine; they fire in every folder, every turn, and no-op outside mounts"
|
|
|
|
class PausedMarker {
|
|
volumes/id/paused
|
|
}
|
|
note for PausedMarker "set by bdrive stop, cleared only by bdrive init (startSync)"
|
|
|
|
class AgentSkills {
|
|
Detect / Install
|
|
embedded SKILL.md
|
|
}
|
|
note for AgentSkills "internal/agentskills — installs the beardrive skill user-level (per-platform skills dir) from the binary's embedded copy; idempotent, refreshed on upgrade"
|
|
|
|
Commands --> AgentSkills : skill install
|
|
Commands --> AgentHooks : hooks install/uninstall (init runs install automatically)
|
|
AgentHooks --> Commands : runs sync and read-log
|
|
Commands --> syncBlocked : sync and read-log gate first
|
|
syncBlocked --> MountRegistry : reads only, never enrolls
|
|
syncBlocked --> PausedMarker : Paused check
|
|
Commands --> openSession : after the gate
|
|
openSession --> MountRegistry : path self-heal (enrolled only)
|
|
Commands --> startSync : init
|
|
startSync --> MountRegistry : enrolls
|
|
startSync --> PausedMarker : clears
|
|
Commands --> PausedMarker : stop sets
|
|
openSession ..> Project : loads
|
|
openSession ..> Device : identity
|
|
openSession ..> Settings : account and token
|
|
```
|