2026-07-08 21:07:40 -07:00
# BearDrive — Google Drive for AI agents
2026-06-11 23:40:26 -07:00
2026-07-07 14:52:32 -07:00
**BearDrive** mounts any folder as a synced volume: its contents stay
2026-07-10 09:55:58 -07:00
synchronized across all your devices and teammates through a BearDrive
**hub** , every change is tracked (who, when, on which device), and
everything keeps working offline. The CLI is `bdrive` ; a hub is a
`bdrive web` server you (or we) run on an object store — clients sync
through it over HTTPS and never touch the storage directly.
2026-06-11 23:40:26 -07:00
2026-07-15 12:05:09 -07:00
What it's for, first and foremost: **sharing context across AI agents** —
give every agent on the team the same folder as memory, and your agent
knows what their agent knows. (People are covered too: any synced file
becomes a public URL that renders as a page.) Notes, plans, findings, and
2026-07-15 11:56:14 -07:00
artifacts follow the team everywhere — and unlike a memory API, they stay
**real files with provenance** : every change is attributed to the human,
agent, and device that made it, and the hub's Insights show what your
agents actually read (and which hot-but-stale docs nobody maintains).
<p align="center">
<img src="docs/assets/insights.png" alt="Knowledge Insights — every file plotted by agent/human reads vs staleness; hot-but-stale docs are the danger zone" width="820">
</p>
| Browse with read heat | Public share pages |
|---|---|
|  |  |
2026-06-11 23:40:26 -07:00
```console
2026-07-15 12:19:39 -07:00
$ bdrive login https://your-hub # once per device — self-host a hub in ~10 min (docs/self-hosting.md)
2026-07-08 13:12:49 -07:00
$ cd ~/workspace && bdrive init
initialized /Users/snow/workspace
project: workspace (p-7f3a2c91)
daemon: running (pid 55434, scan 3s, remote sync 10s)
2026-06-11 23:40:26 -07:00
```
2026-07-18 16:25:13 -07:00
> BearDrive Cloud — zero-setup, bare `bdrive login`, free personal
> workspace on signup — at [beardrive.ai](https://beardrive.ai). Or
> self-host your own hub.
2026-07-15 12:19:39 -07:00
2026-06-11 23:40:26 -07:00
On another machine:
```console
2026-07-15 12:19:39 -07:00
$ bdrive login https://your-hub && cd ~/workspace && bdrive init
2026-07-08 13:12:49 -07:00
# … connect the same project; the files appear and stay in sync
2026-06-11 23:40:26 -07:00
```
## Features
2026-07-08 13:12:49 -07:00
- **Any folder is a project** — `bdrive init` turns any folder into a synced
project. Files are *real files on disk* : every tool, editor, and agent can
use them with zero integration work. Rename or move the folder freely —
state is keyed by a stable id, never the path.
2026-07-10 09:55:58 -07:00
- **Multi-device sync** — devices converge through a shared hub. Each
device only writes its own append-only journal, so no locking service is
needed; the hub can be backed by any object store.
2026-07-08 13:12:49 -07:00
- **Change tracking** — `bdrive log` and the web UI's History view show
which account changed which file, when, from which device (name, OS, IP).
Content is stored content-addressed, so every version is retained — view
or download any point in a file's history.
2026-07-10 09:55:58 -07:00
- **Cloud-provider agnostic** — a hub can store on Amazon S3 (`s3://` ),
Google Cloud Storage (`gs://` ), any S3-compatible store (MinIO, Cloudflare
R2 via `AWS_ENDPOINT_URL` ), or a plain shared directory (`file://` , e.g. a
NAS). Clients never see it.
2026-06-11 23:40:26 -07:00
- **Offline-first** — the working folder is always fully usable with no
network. Changes are journaled locally and pushed when the remote becomes
reachable again.
- **Conflict-safe** — concurrent edits resolve deterministically
(last-writer-wins), and the losing version is preserved as a
2026-07-08 07:13:00 -07:00
`name.bdrive-conflict-<device>-<time>` file. Nothing is silently dropped.
2026-07-08 13:12:49 -07:00
- **Selective sync** — a gitignore-style `.bdriveignore` opts files out, and
`bdrive init --shared <dir>` (or the interactive prompt) narrows sync to
one shared subfolder.
2026-06-11 23:40:26 -07:00
- **macOS & Linux.**
## Install
```sh
2026-07-07 15:02:40 -07:00
brew install runbear-io/tap/beardrive # macOS (and Linuxbrew); installs the `bdrive` CLI
2026-06-11 23:40:26 -07:00
```
or from source:
```sh
2026-07-07 15:02:40 -07:00
go install github.com/runbear-io/beardrive/cmd/bdrive@latest
2026-06-11 23:40:26 -07:00
```
## Quick start
```sh
2026-07-15 12:13:44 -07:00
# 1. Sign this device in against your hub (once per device).
# Self-host a hub in ~10 minutes (docs/self-hosting.md), then:
2026-07-18 16:25:13 -07:00
bdrive login
# (BearDrive Cloud: sign up in the browser, get a free personal
# workspace automatically. Self-hosting? bdrive login https://your-hub)
2026-06-11 23:40:26 -07:00
2026-07-08 13:12:49 -07:00
# 2. Start syncing a project — interactive: create or connect a project,
# sync the whole folder or just ./shared. Re-run any time to resume.
cd ~/my-project && bdrive init
2026-06-11 23:40:26 -07:00
2026-07-08 13:12:49 -07:00
# 3. Work normally — create, edit, delete files with any tool.
echo "remember this" > memory.md
2026-07-15 12:19:39 -07:00
# On every other device: `bdrive login https://your-hub` once, then bdrive init in a folder
2026-07-08 13:12:49 -07:00
# and connect the same project.
2026-06-11 23:40:26 -07:00
# See what changed, who changed it, and from which device
2026-07-08 13:12:49 -07:00
bdrive log
2026-06-11 23:40:26 -07:00
# Check sync state and the daemon
2026-07-07 15:02:40 -07:00
bdrive status
2026-06-11 23:40:26 -07:00
2026-07-24 15:53:23 +09:00
# Stop syncing — pauses everything, including agent turn hooks
# (files stay on disk; bdrive init resumes any time)
2026-07-08 13:12:49 -07:00
bdrive stop
2026-06-11 23:40:26 -07:00
```
2026-07-08 13:12:49 -07:00
Renaming or moving a project folder is safe: state is keyed by a stable
project id, never the path. The daemon notices the move, steps aside, and
the next `bdrive init` (or any bdrive command) at the new location resumes
exactly where it left off — zero re-scan, zero spurious changes.
2026-06-11 23:40:26 -07:00
### Credentials
2026-07-15 12:05:09 -07:00
beardrive uses each provider's standard credential chain — nothing beardrive-specific.
Note: **client devices always use an `https://` hub remote** — the
`s3` /`gs` /`file` rows below are how the *hub operator* configures the
hub's own storage, never something a syncing client points at directly:
2026-06-11 23:40:26 -07:00
| Remote | Credentials |
|---|---|
| `s3://bucket/prefix` | `AWS_PROFILE` , `~/.aws/credentials` , env vars, IAM roles. S3-compatible stores via `AWS_ENDPOINT_URL` . |
| `gs://bucket/prefix` | Application Default Credentials (`gcloud auth application-default login` ) or `GOOGLE_APPLICATION_CREDENTIALS` . |
| `file:///path` | none — any local or network-mounted directory |
2026-07-08 07:13:00 -07:00
| `https://host:port/p/<id>` | none — syncs through a bdrive web hub; only the server holds storage credentials (see [The sync hub and `bdrive init` ](#the-sync-hub-and-bdrive-init )) |
2026-06-11 23:40:26 -07:00
## Commands
| Command | Description |
|---|---|
2026-07-22 09:35:48 -07:00
| `bdrive login [server-url]` | Sign this device in (browser flow; `--device` forces the code flow, and shells without a TTY fall back to it automatically; default server beardrive.ai — the managed cloud, free personal workspace on signup; pass your hub URL to self-host). Switch hubs with `bdrive login <new-url>` |
2026-07-10 09:01:11 -07:00
| `bdrive logout` | Sign this device out — clear the saved token/account (`--forget` also drops the remembered server) |
2026-07-08 13:12:49 -07:00
| `bdrive init [folder]` | Create/connect a project and start syncing — interactive on a TTY, flags (`--name/--project/--shared/--yes` ) for scripts; re-run to resume |
2026-07-24 15:53:23 +09:00
| `bdrive stop [folder]` | Stop syncing, including agent sync hooks (files stay; `bdrive init` resumes) |
2026-07-14 11:29:54 -07:00
| `bdrive url [path]` | Internal hub link for a file/folder (sign-in + membership required; `--sync` pushes first; no arg = project home). Computed locally |
2026-07-08 15:02:13 -07:00
| `bdrive share <file>` | Public URL for a synced file (`--list` , `--revoke` , `--expires` ) |
2026-07-16 10:28:25 -07:00
| `bdrive sync [folder]` | Run one sync cycle now. `--note <text>` stamps session context (e.g. an agent session id) onto changes — shown in `bdrive log` and hub history; keeps applying to daemon-committed changes until `--note-ttl` (default 30m) expires. `--hook <label>` is agent-hook plumbing: event JSON on stdin, sync + note, gated-link formula (Claude Code hook JSON) on stdout |
2026-07-11 14:54:32 -07:00
| `bdrive hooks [install]` | Register turn-boundary sync hooks with detected agent platforms (Claude Code, Codex, Gemini CLI, Hermes) — pull each turn, push after edits, session-note stamping, agent-read tracking; idempotent (`--agent` overrides detection) |
2026-07-19 14:30:06 -07:00
| `bdrive skill [install]` | Install the `beardrive` skill into detected agent platforms (`~/.codex/skills/beardrive/SKILL.md` and friends) so the agent can do the setup itself — sign in, `bdrive init` , and register the sync hooks; idempotent (`--agent` overrides detection) |
2026-07-13 15:05:05 -07:00
| `bdrive read-log [folder]` | Hook plumbing: queue agent file reads from a hook event (JSON on stdin) for the hub's read heatmap — native reads, grep matches, and files named in shell commands; drained on the next sync. Registered by `bdrive hooks install` |
2026-07-08 13:12:49 -07:00
| `bdrive status [folder]` | Projects, daemon state, pending changes |
| `bdrive log [folder] [-p path] [-n N]` | Change history: account, device, time, file |
2026-07-23 19:04:05 -07:00
| `bdrive export [folder]` | Export the whole project — every device's journal, all blobs, full history — from its hub to a portable `.tar.gz` (`-o` names the file) |
| `bdrive import <archive>` | Import an export archive as a new project on the hub you're logged into (`--name` overrides); history and authorship carry over. Move projects between hubs — cloud → self-hosted or back — with `export` + `login` + `import` |
2026-07-08 13:12:49 -07:00
| `bdrive web [folder \| storage-root-url]` | Web server: viewer (rendered markdown, downloads, history), uploads, multi-project sync hub |
2026-07-22 09:35:48 -07:00
| `bdrive whoami` | Signed-in account and device identity used in change tracking |
| `bdrive version` | Print the version (also `bdrive --version` ) |
2026-06-11 23:40:26 -07:00
2026-07-07 14:05:36 -07:00
## Project files
2026-06-17 08:24:29 -07:00
2026-07-07 14:05:36 -07:00
Each mounted folder carries its own settings, so configuration travels with
the project:
2026-06-17 08:24:29 -07:00
2026-07-08 13:12:49 -07:00
- **`.bdrive/` ** — the folder's settings directory: `config.json` holds the
**stable mount id** plus project/remote/include settings. Written by
`bdrive init` , safe to hand-edit (a running daemon picks changes up
automatically). Never synced, and it holds no credentials (the session
token stays in `~/.bdrive` ). Because everything is keyed by the mount id,
the folder can be renamed or moved freely; copy it to another machine and
`bdrive init` resumes the same project.
2026-07-08 07:13:00 -07:00
- **`.bdriveignore` ** — gitignore-style opt-out list at the mount root. Syncs
2026-07-07 14:05:36 -07:00
like a normal file, so every device shares the same rules. Supports `#`
comments, `*` , `**` , `?` , trailing `/` for directories, leading `/` (or any
`/` ) for root-anchoring, and `!` to re-include.
```jsonc
2026-07-08 13:12:49 -07:00
// .bdrive/config.json
{ "id" : "m-5a10b713" , "volume" : "notes" ,
"remote" : "https://drive.example.com/p/p-7f3a2c91" , "include" : [ "shared/" ] }
2026-07-07 14:05:36 -07:00
```
Opting out is non-destructive: when a pattern starts matching an
already-synced file, the file stops syncing but is deleted nowhere.
2026-07-08 07:13:00 -07:00
## Web server
2026-07-07 14:05:36 -07:00
2026-07-08 07:13:00 -07:00
`bdrive web` serves a website — browse folders and files, read markdown
rendered Obsidian-style (including `[[wikilinks]]` , task lists, and
tables), download any file — and, pointed at a storage root, becomes a
**multi-project sync hub** . It is read-only unless started with `--upload` .
2026-07-07 14:05:36 -07:00
```sh
2026-07-08 07:13:00 -07:00
bdrive web # serve the current directory (viewer)
bdrive web ./notes # serve a folder from disk (viewer)
bdrive web -c config.json # everything from a config file
bdrive web s3://my-bucket/root --upload # multi-project sync hub
2026-07-07 14:05:36 -07:00
```
2026-07-08 07:13:00 -07:00
With a folder it serves files straight from disk — on a BearDrive mount the
daemon keeps them fresh, so this is the simplest read-only deployment (no
cloud credentials on the serving machine). With a storage root URL it runs
in hub mode, described below.
2026-07-07 14:05:36 -07:00
Flags: `--addr` (default `:4173` ), `--volume` (display name), `--refresh`
(listing cache, default `10s` ), `--dir` / `--remote` (explicit forms of
2026-07-08 07:13:00 -07:00
the positional argument), `--upload` (allow client writes, off by default),
`--upload-ttl` (presigned-URL lifetime, default `15m` ), `--projects-db`
(hub project registry file, default `$BDRIVE_HOME/projects.json` ),
`-c/--config` (read all of the above from a JSON file; explicit flags win):
```jsonc
// bdrive web -c config.json
{
"remote" : "s3://my-bucket/root" , // storage root (hub) — or "dir": "./folder" (viewer)
"addr" : ":4173" ,
"upload" : true ,
"upload_ttl" : "15m" ,
"refresh" : "10s" ,
2026-07-08 13:12:49 -07:00
"projects_db" : "/var/lib/bdrive/projects.json" ,
2026-07-08 17:10:12 -07:00
"share_rpm" : 120 , // per-IP rate limit on public /s/* links
2026-07-08 13:12:49 -07:00
"auth" : { // optional knobs; hub auth is always on
2026-07-09 16:57:49 -07:00
// Signup is invite-only by default. To allow self-service signup,
// open it WITH a gate (an ungated open hub is refused at startup):
2026-07-08 13:12:49 -07:00
"allow_signup" : true ,
2026-07-09 16:57:49 -07:00
"allowed_domains" : [ "example.com" ], // only these domains may sign up
"require_approval" : true , // …and an admin must approve each one
2026-07-08 13:12:49 -07:00
"users_db" : "/var/lib/bdrive/auth.json" ,
2026-07-09 16:57:49 -07:00
"admins" : [ "admin@example.com" ],
2026-07-08 13:12:49 -07:00
"smtp" : { "host" : "smtp.example.com" , "port" : 587 ,
"user" : "drive@example.com" , "pass" : "…" , "from" : "drive@example.com" }
2026-07-11 14:54:32 -07:00
},
"reads" : { // read heatmap telemetry (hub mode)
"enabled" : true , // default true; aggregate counts only
"retention_days" : 400 // daily buckets older than this fold into all-time totals
2026-07-08 13:12:49 -07:00
}
2026-07-08 07:13:00 -07:00
}
```
### The sync hub and `bdrive init`
In hub mode the server hosts many **projects** on one storage root — each
project's data lives under its own prefix (`<root>/<project-id>/` ), and a
file-backed registry (`projects.json` , loaded at start, rewritten
atomically on every change) maps project ids to names. Client devices sync
whole folders through the hub without ever knowing where the storage is or
holding any cloud credentials; the server device is the only one configured
with the bucket.
2026-07-08 17:10:12 -07:00
Projects are walled by **organization** : every project belongs to one org
(file-backed `orgs.json` ), and only that org's members — accounts with the
`owner` or `member` role — can see, browse, or sync it. Your first
`bdrive init` creates an org for you automatically; an owner invites
teammates from the web UI (the org name in the sidebar footer — Invite
2026-07-09 14:19:07 -07:00
mints an expiring join link, `/join/<token>` , that any signed-in account
2026-07-08 17:10:12 -07:00
can open to become a member). A hub upgraded from an earlier version
sweeps its existing projects into a `default` org that all existing
accounts join, so nothing breaks. Public share links stay outside the
wall on purpose.
2026-07-08 07:13:00 -07:00
```sh
# On the server device (knows the storage)
bdrive web -c config.json
# On each client device (knows only the server) — one command does it all:
bdrive login https://drive.example.com:4173 # once per device
cd ~/some-project && bdrive init # once per project
```
2026-07-08 13:12:49 -07:00
`bdrive login` signs the device in and remembers the server (`settings.json`
2026-07-15 12:19:39 -07:00
under the bdrive home; bare `bdrive login` defaults to beardrive.ai — the
2026-07-18 16:25:13 -07:00
managed cloud, where signup auto-creates a free personal workspace; pass
your hub's URL to use a self-hosted hub instead — `--status` shows the
current server and account). To move to a **different
2026-07-10 09:01:11 -07:00
hub**, run `bdrive login <new-url>` and then re-run `bdrive init` in each
folder to connect it to a project there; `bdrive logout` signs out entirely.
`bdrive init` then, per
2026-07-08 13:12:49 -07:00
project, walks you through it on a terminal: **create a new project or
connect an existing one** (picked from the server's list), and **sync the
whole folder or only a shared subfolder** (e.g. `./shared` ). Every question
has a flag (`--name` , `--project` , `--shared` , `--yes` ), and without a TTY
init never prompts — it creates-or-joins a project named after the folder
and syncs everything. It writes `.bdrive/config.json` , seeds a starter
`.bdriveignore` (node_modules, build dirs, caches, `.env*` ), and starts the
daemon — local changes are detected within seconds, and the Claude Code
plugin syncs at every session step. Not signed in yet? init runs the login
flow first.
2026-07-08 07:13:00 -07:00
Under the hood the `https://` remote speaks the hub's per-project
`/api/p/<id>/store` API — journal reads/writes relay through the server,
blob uploads go direct to the object store via the same short-lived
presigned URLs browser uploads use (falling back to relaying when the
backend can't presign). Client pushes and project creation require the
server to run with `--upload` ; against a read-only hub, clients still pull
and their pushes wait (offline semantics) until allowed.
2026-07-08 15:02:13 -07:00
### Sharing files by URL
2026-07-14 11:29:54 -07:00
For teammates, every synced file already has an internal link — the hub
viewer URL, gated by sign-in and the project's org membership:
```console
$ bdrive url wiki/report.html
https://drive.example.com/p-1a2b3c4d/wiki/report.html
```
It's computed locally (no network), always shows the latest synced
content, and is the link agents should drop in their replies when they
create an artifact in the shared folder (`--sync` pushes first so a
just-created file resolves immediately).
For people **outside** the hub, any synced file can instead be shared
with a public link — hand someone the URL and they see the file, no
account needed:
2026-07-08 15:02:13 -07:00
```console
$ bdrive share wiki/report.html
https://drive.example.com/s/eacc1df3ee6a6ebbdacc535c2796dc30
```
Links always serve the file's **latest** synced content (right for wiki
pages and living reports), and live until revoked — `bdrive share --list`
and `--revoke <token-or-url>` manage them, `--expires 24h` makes one
self-destruct. The web UI has a Share button on every file.
2026-07-08 17:10:12 -07:00
Shared HTML renders as a real page, markdown renders like the viewer
(with a small "Shared with BearDrive" footer; raw HTML is served
byte-for-byte), PDFs open inline. Rendering is sandboxed: `/s/*` responses
carry a strict CSP, never see auth cookies, and sit behind a generous
per-IP rate limit (`share_rpm` ), so a malicious shared file's scripts
can't touch hub sessions and a scraper can't turn the hub into a CDN.
Any org member can mint links, and a link is public to whoever has the
URL — don't share folders that hold secrets, and note a LAN-bound hub
means LAN-only links.
2026-07-08 15:02:13 -07:00
### Claude Code integration
The BearDrive plugin (`/plugin marketplace add runbear-io/beardrive` )
makes agents fluent in all of this, and ** `/beardrive:install` ** sets a
project up conversationally: installs the CLI, signs in, creates or
connects a project (whole folder or a shared subfolder like `wiki/` ),
offers to document the shared folder in CLAUDE.md so agents proactively
put shareable artifacts there, and registers project-level hooks in
`.claude/settings.json` — a blocking pull when you submit a prompt (Claude
reads fresh team files) and an async push after every file edit (artifacts
are on the server seconds after Claude writes them), for every teammate
whether or not they installed the plugin. The payoff: "write a report and
share it" becomes Claude generating `wiki/report.html` and replying with a
public URL.
2026-07-08 17:10:12 -07:00
The web UI lists your orgs' projects in the sidebar (⌘K opens a command
palette: fuzzy file search, project switching, share/history/upload
actions); selecting one browses that project's files, and the **History**
view shows every change — which
2026-07-08 13:12:49 -07:00
account made it, when, from which device (name, OS, and the IP the server
observed), with view/download of any past version (content is
content-addressed and retained forever; reverting to a version is the next
phase and the API is already shaped for it). Folder rows have a history
shortcut for a subtree feed; the topbar button shows the current file's
versions or the whole project feed.
2026-07-11 14:54:32 -07:00
Hubs also track **read heat** : viewer opens and downloads count as human
reads, share-link hits as share reads, and agent tool reads (reported by
the sync hooks via `bdrive read-log` ) as agent reads — sync replication
never counts. Folder listings show heat dots and 30-day read counts to
2026-07-12 07:36:37 -07:00
every member, and admins / org owners get an **Insights** dashboard
(⋯ menu), four sections with an all/human/agent lens: a **treemap** of
every file (cell size = reads, color = staleness, ⚠ on hot+stale — click
through to any file), the **reads × freshness** scatter whose hot-but-stale
quadrant is the knowledge people rely on that nobody maintains, the
**hot path** (top files by reads, agent/human split — effectively the
team's agent context window), and an **agent coverage matrix** (which
agent devices read which folders). The API
2026-07-11 14:54:32 -07:00
(`GET /api/p/<id>/heat?prefix=&days=` ) exposes only aggregate counts,
2026-07-12 07:36:37 -07:00
distinct-reader counts, and last-read times — never who read what;
`?by=device` adds the agent-only per-device folder breakdown (device
identity is already public via history; human emails never appear).
2026-07-11 14:54:32 -07:00
2026-07-15 12:13:44 -07:00
### Authentication & database
2026-07-08 13:12:49 -07:00
2026-07-15 12:13:44 -07:00
Hubs always require sign-in — every change is attributed to a real
account. **Signup is invite-only by default** (the safe posture for a
public URL); self-service signup opens only with a gate (admin approval,
or allowed domains + email verification). Hub metadata (accounts,
projects, orgs, shares) lives in a file-backed store by default, or
SQLite/Postgres (incl. Supabase) via the `database` config block.
2026-07-09 16:57:49 -07:00
2026-07-15 12:13:44 -07:00
Full reference — the three signup postures, SMTP, admins, CLI device
sign-in, and database selection: ** [docs/self-hosting.md ](docs/self-hosting.md )**.
2026-07-09 16:57:49 -07:00
2026-07-10 06:54:49 -07:00
2026-07-08 07:13:00 -07:00
### Uploads
The browser client is deliberately storage-blind: it never sees the remote
URL, bucket, or any credentials. On page load it fetches `/api/config` and
follows whatever the server allows.
With `--upload` set, the server decides per upload how the bytes travel:
- **Direct** — for backends that can presign (S3 and S3-compatible stores;
GCS when the server runs with credentials that can sign, e.g. a service
account): the server mints a short-lived presigned `PUT` URL for the
content-addressed blob (`blobs/<sha256>` ), the browser uploads straight
to the object store, then asks the server to commit. The commit verifies
the blob actually exists and appends a `put` op to the *server's own*
journal — the blobs-before-journal ordering and the one-writer-per-journal
invariant both hold. Expired URLs are refused by the store; the client
just re-runs init. Direct uploads to a bucket also need a CORS rule on
the bucket allowing `PUT` from the viewer's origin.
- **Through the server** — `file://` remotes and plain-folder serving can't
presign, so the client sends content to the server, which stores it
(object store + journal, or straight to disk for a served folder, where
the daemon will pick it up like any local edit).
2026-07-07 14:05:36 -07:00
## Claude Code plugin
2026-07-07 15:02:40 -07:00
Install beardrive support in Claude Code with two commands:
2026-07-07 14:05:36 -07:00
```
2026-07-07 15:02:40 -07:00
/plugin marketplace add runbear-io/beardrive
/plugin install beardrive@beardrive
2026-07-07 14:05:36 -07:00
```
The plugin sets up everything at once:
2026-07-08 15:09:11 -07:00
- **`/beardrive:install` ** — the full team setup, conversationally: CLI,
sign-in, project init (whole folder or a shared subfolder like `wiki/` ),
2026-07-12 18:30:07 -07:00
a consent-gated agent orientation — a synced `AGENTS.md` mapping the
shared folder plus a repo-root pointer to it — and project-level sync
2026-07-08 15:09:11 -07:00
hooks in `.claude/settings.json` .
- **`/beardrive:init [folder] [--name/--project/--shared]` ** — just start
syncing a project; `/beardrive:status` diagnoses problems.
2026-07-07 14:05:36 -07:00
- **Turn-boundary sync hooks**, registered automatically: a blocking pull
when you send a message (Claude always reads fresh files) and an async
2026-07-08 15:09:11 -07:00
push when the turn ends. The hook no-ops instantly in folders without a
`.bdrive/` project, so it's safe globally.
2026-07-07 15:02:40 -07:00
- **The `beardrive` skill** ([plugin/skills/beardrive ](plugin/skills/beardrive/SKILL.md )),
2026-07-08 15:09:11 -07:00
covering init/stop/sync, sharing by URL, backends and credentials,
selective sync, and troubleshooting. Working in a clone of this repo
picks the same skill up automatically via `.claude/skills/` .
2026-06-17 08:24:29 -07:00
2026-07-19 14:30:06 -07:00
## Other agents: Codex, Gemini CLI, Hermes
No terminal needed here either — the setup is one paste. Start the agent in
the folder you want the files and give it:
```
Set up BearDrive in this folder.
1. If `bdrive` is missing, install it: brew install runbear-io/tap/beardrive
(no Homebrew? grab the release binary for this OS/arch from
https://github.com/runbear-io/beardrive/releases)
2. bdrive skill install # so you know the CLI next time
3. bdrive login --device https://your-hub # show me the code and the URL
4. bdrive init --project <project-id>
5. bdrive hooks install # don't skip this - it's what syncs every turn
Then tell me what got set up.
```
The commands ride inside the prompt because these agents ship no BearDrive
knowledge — but the user copies one thing, and the agent handles every
deviation (already installed, no Homebrew, sign-in, wrong folder). Step 2 is
the durable part: `SKILL.md` is a cross-agent format, and `bdrive skill
install` writes the very skill the Claude plugin ships to each detected
platform's user-level skills directory (`~/.codex/skills/beardrive/SKILL.md` ,
`~/.gemini/…` , `~/.hermes/…` , `~/.claude/…` ), so from then on "share this
file" or "what changed?" just works. Step 5 is the one people skip when they
copy commands by hand, which is exactly why the agent runs it.
A project's home page in the web UI shows this with the hub URL and project
id already filled in (plus the plain-terminal version). `bdrive skill` and
`bdrive hooks` print what's set up on this machine; re-run either after a CLI
upgrade to refresh.
2026-06-11 23:40:26 -07:00
## How it works
```
working folder ←materialize/scan→ local volume store ←push/pull→ object store
2026-07-08 07:13:00 -07:00
(real files) ~/.bdrive/volumes/<vol> s3:// gs:// file://
2026-06-11 23:40:26 -07:00
├─ blobs/ content-addressed (sha256)
├─ journal/ one append-only op log per device
├─ state.json what's materialized
└─ sync.json lamport clock + push cursor
```
- Every change becomes an **op** (`put` /`delete` ) in this device's
append-only journal, stamped with a lamport clock, wall-clock time, device
ID, and author. File content goes into a content-addressed blob store.
- A **sync** uploads new blobs, then the journal; it downloads other
devices' journals and any blobs it's missing. Since each device writes
only its own journal, there are no concurrent writers per object and any
dumb object store suffices.
- The folder's state is a deterministic **replay** of all journals ordered
by `(lamport, time, device)` — every device converges to the same view.
Concurrent edits keep the last writer at the path; the loser is preserved
as a conflict-copy file by the device that detects the overlap.
- A per-mount **daemon** scans the folder every few seconds (cheap
2026-06-12 08:46:44 -07:00
size+mtime check) and exchanges with the remote every ~10s — or
immediately after local edits. Tune with --scan-interval and
2026-07-08 13:12:49 -07:00
--remote-interval on `bdrive init` .
2026-06-11 23:40:26 -07:00
2026-07-07 15:02:40 -07:00
### What beardrive does not sync
2026-06-11 23:40:26 -07:00
`.git` directories (per-file LWW would corrupt repositories), `.DS_Store` ,
2026-07-10 21:42:36 -07:00
the `.bdrive` settings file, its own temp files, nested mounts (a
subdirectory with its own `.bdrive/config.json` syncs only through its own
project — the parent never scans into it, writes over it, or propagates
deletes for it), and anything excluded by `.bdriveignore` or omitted from an
`include` list. Empty directories are not tracked (like git).
2026-06-11 23:40:26 -07:00
## Roadmap
2026-07-15 11:56:14 -07:00
See [ROADMAP.md ](ROADMAP.md ) — the public, dated roadmap, including the
items we'd love help with. Highlights: `beardrive restore <path>@<time>`
(time travel — all content is already retained), FUSE/NFS mount mode,
journal compaction & blob GC, per-path access scopes for multi-agent
setups.
2026-06-11 23:40:26 -07:00
## Development
2026-07-15 11:56:14 -07:00
Contributions welcome — see [CONTRIBUTING.md ](CONTRIBUTING.md ) for the
build/test workflow and the rules that matter, [ROADMAP.md ](ROADMAP.md )
for where help is wanted, and [CHANGELOG.md ](CHANGELOG.md ) for what
shipped when. Self-hosting a hub: [docs/self-hosting.md ](docs/self-hosting.md ).
2026-06-11 23:40:26 -07:00
```sh
go build ./...
go test ./...
```
The integration tests in `internal/syncer` simulate multiple devices syncing
through a `file://` remote, including offline operation and concurrent-edit
2026-07-08 07:13:00 -07:00
conflicts. Set `BDRIVE_HOME` to relocate all beardrive state (used heavily in tests).
2026-06-11 23:40:26 -07:00
2026-07-13 10:57:21 -07:00
### Web frontend
The hub's web UI is a React + TypeScript app in `internal/webapp/frontend`
2026-07-19 13:15:24 -07:00
(Vite + Tailwind v4 + shadcn/ui components owned in-repo; TanStack
query/table/virtual, react-hook-form + zod, cmdk, sonner, lucide-react —
routing stays a small in-repo history router). Its
2026-07-13 10:57:21 -07:00
**built output is committed** at `internal/webapp/static` , the `go:embed`
target, so building or `go install` -ing the binary never needs Node.
Only when changing `frontend/src` :
```sh
cd internal/webapp/frontend
npm install
npm run dev # hot-reload dev server, proxying /api to a local hub
# (BDRIVE_DEV_PROXY=http://localhost:8993 to point elsewhere)
npm run build # rebuild internal/webapp/static — commit the result
npm run e2e # Playwright suite; starts its own seeded hub on :8993
./check-dist.sh # verify the committed static/ is fresh (pre-release check)
```
2026-06-11 23:40:26 -07:00
## License
2026-07-08 16:52:31 -07:00
GNU AGPL-3.0 — Copyright 2026 Runbear, Inc. See [LICENSE ](LICENSE ).
2026-07-15 11:56:14 -07:00
We chose AGPL-3.0 deliberately: it keeps BearDrive fully open and
self-hostable forever while preventing a cloud provider from offering a
closed BearDrive-as-a-service. The managed service at beardrive.ai funds
the project; the code stays open.
2026-07-08 16:52:31 -07:00
Everything in this repo is open source and self-hostable: a complete BearDrive
server for one organization's deployment, teams included. The managed service
at beardrive.ai is the same core plus what only makes sense as an operated
service — hosting, PropelAuth SSO, billing and plan quotas, backups, and
support. Provider-specific and billing code stays out of this repo permanently;
the server exposes interfaces (`AuthProvider` , `QuotaProvider` ) that the
managed deployment fills in.