feat(hooks): user-scope agent sync hooks, one-command setup, --only scoping (#71)

* feat(hooks): register agent sync hooks per machine, not per project

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

* docs: fix stale claims an audit found against the new CLI

An audit of every doc surface against the code turned up claims that the
user-scope hook move and the one-command init made false: project-level
hooks "riding the repo", the Claude trust prompt, Codex's //hooks project
layer, `--no-hooks` skipping the skill (it does not), prune reconciling
against a per-device scope (it now refuses on a scoped project), and
`--scan-interval`/`--remote-interval` documented as init flags when they
only exist on `bdrive daemon run`.

Also documents the surface added today — `--server`, `bdrive hooks
uninstall`, and the plugin's PreToolUse auto-approval — refreshes the two
sample `init` transcripts to the real output, and corrects hook matchers
that had drifted from agenthooks.go.

`bdrive scope` told users to narrow an existing mount with `bdrive init .
--only <dirs>`, which resume then ignored — a dead end. Init now applies
--only on resume, writing the scope block, so the advice works.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016aYntCWwdUhpzUfEk3ddyJ

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Snow W. Lee (Sungwon)
2026-07-29 10:08:45 +09:00
committed by GitHub
co-authored by Claude Opus 5
parent 22461a3b4f
commit 4e34d03e14
52 changed files with 2601 additions and 691 deletions
+29 -26
View File
@@ -1,10 +1,10 @@
---
description: Start syncing a project in this folder — create a new BearDrive project or connect an existing one, whole folder or shared subfolders, and start the sync daemon
argument-hint: "[folder] [--name <project> | --project <p-id>] [--shared <dirs>]"
description: Start syncing a project in this folder — create a new BearDrive project or connect an existing one, the whole folder or only some subfolders, and start the sync daemon
argument-hint: "[folder] [--name <project> | --project <p-id>] [--only <dirs>]"
---
Start syncing a project with BearDrive. Arguments: `$ARGUMENTS` (optional
folder, optional `--name`/`--project`/`--shared`).
folder, optional `--name`/`--project`/`--only`).
Follow these steps:
@@ -32,7 +32,7 @@ Follow these steps:
own project; never a brain root, and one enrichment owner per shared
folder (everyone else indexes read-only — see the skill).
- **OKF wiki** (markdown with OKF frontmatter) → offer: connect the
wiki dir via `--shared`, or keep it PR-gated and create a new shared
wiki dir by mounting it, or keep it PR-gated and create a new shared
folder.
- **Wiki-ish folder** (`docs`/`wiki`/`notes` full of markdown) → check
`git log -- <dir>`; dormant → recommend connecting it, active PR
@@ -43,10 +43,11 @@ Follow these steps:
4. **Initialize** — two hard rules:
- **Never sync a repo root**: inside a repo, knowledge syncs as
scoped subfolders via `--shared` (one or more — several folders can
share one project when the same people should see all of them;
folders needing different access go in separate projects). A
- **Never sync a repo root**: inside a repo, knowledge syncs as a
mounted subfolder (`bdrive init wiki`), or as the root narrowed
with `--only` when several folders belong to one project and the
same people should see all of them (folders needing different
access go in separate projects). A
dedicated knowledge folder (empty dir, standalone vault) may be the
mount itself.
- **One transport per folder**: a git-tracked dir must leave git
@@ -56,21 +57,23 @@ Follow these steps:
```sh
bdrive init --name <project> --yes # dedicated knowledge folder
bdrive init --name <project> --shared wiki # in a repo: only ./wiki syncs
bdrive init --name <project> --shared wiki,docs # several shared subfolders, one project
bdrive init wiki --name <project> --yes # ./wiki is the project
bdrive init . --name <project> --only wiki,docs --yes # narrow this folder to those subfolders
```
5. **Register agent sync hooks**: run `bdrive hooks install <folder>`. It
detects the agent platforms in use (Claude Code, Codex, Gemini CLI,
Hermes — by their config dirs in the project or home) and idempotently
merges beardrive's sync hooks into each platform's own hook config, so
files pull at every turn start, push after edits, every change is
stamped with the agent session that made it, and agent file reads — native
reads, grep matches, and files named in shell commands — feed the hub's
read heatmap (queued locally by `bdrive read-log`, reported on the next
sync). Tell the user which platforms got hooks; if Codex is
among them, mention they must run `/hooks` inside Codex once to trust
the project's `.codex` layer.
5. **Confirm the sync hooks**: `bdrive init` registered them for every agent
platform it detected, in that platform's **user** config
(`~/.claude/settings.json`, `~/.codex/hooks.json`, `~/.gemini/settings.json`,
`~/.hermes/config.yaml`) — once per machine, covering every session in
every folder, with nothing written inside the project. Files pull at every
turn start, push after edits, every change is stamped with the agent
session that made it, and agent file reads feed the hub's read heatmap
(queued locally by `bdrive read-log`, reported on the next sync). Read
init's output and tell the user which platforms are covered. If Codex is
among them, pass on that its hooks are experimental and off by default —
`[features] codex_hooks = true` in `~/.codex/config.toml`, then trust the
hook when Codex asks. `bdrive hooks` shows the status table;
`bdrive hooks uninstall` removes them.
6. **Verify**: run `bdrive status <folder>` and confirm the daemon is
running and pending is 0. Summarize: project name/id, what syncs, and
@@ -79,11 +82,11 @@ Follow these steps:
the skill's "Teaching agents the folder" section): a synced
`<shared>/AGENTS.md` mapping the folder — draft it if this user is
creating the project, read and follow it if joining — and, for
`--shared` mounts inside a repo, a short pointer to it in the repo
mounts inside a repo, a short pointer to it in the repo
root's `AGENTS.md`/`CLAUDE.md` (the only file Codex loads, and what
makes any agent aware the folder matters). Then tell the user how
teammates connect (invite link → `bdrive init` → same `--shared`
scope, which is per-device).
teammates connect (invite link → `bdrive init`; the scope rides
`.bdriveignore`, so it matches automatically).
For the full team setup (the AGENTS.md orientation + per-project sync
hooks in `.claude/settings.json`), suggest `/beardrive:install` instead.
For the full team setup (the AGENTS.md orientation + the sync hooks init
registers in `~/.claude/settings.json`), suggest `/beardrive:install` instead.
+81 -63
View File
@@ -1,6 +1,6 @@
---
description: Set up BearDrive for this project — install the CLI, sign in, create/connect a project, optionally document the shared folder in CLAUDE.md, and register project-level sync hooks so every teammate's files stay fresh during Claude sessions
argument-hint: "[project-name] [--shared <dirs>]"
description: Set up BearDrive for this project — install the CLI, sign in, create/connect a project, optionally document the shared folder in CLAUDE.md, and register machine-wide sync hooks in your agent's user config so files stay fresh in every session
argument-hint: "[project-name] [--only <dirs>]"
---
Set up BearDrive for the current project, end to end. Work through these
@@ -27,22 +27,46 @@ browser window is coming, then sign in:
## 3. Initialize the project
If `$ARGUMENTS` gives a project name and/or `--shared <dirs>` (one or more,
comma-separated or repeated), use them.
Otherwise ask the user two questions (or infer from their request):
If `$ARGUMENTS` gives a project name and/or `--only <dirs>` (comma-separated),
use them.
Otherwise ask the user two questions:
- **Create a new project or connect an existing one?** (`bdrive init
--name <name>` creates-or-joins by name; `bdrive init --project <p-id>`
connects by id.)
- **Sync the whole folder, or only shared subfolders?** Hard rule:
**never sync a repo root** — inside a repo, knowledge always syncs as
scoped subfolders via `--shared`. Whole-folder is only for a dedicated
knowledge folder (an empty dir, a standalone vault) that is the mount
itself. Don't ask open-endedly: scan the repo for existing knowledge
folders (`wiki/`, `docs/`, `notes/`, `handbook/`, an Obsidian vault —
markdown-heavy, not source code) and propose the candidates for
confirmation, e.g. "I found `./wiki` and `./docs` — sync both?"
(`--shared wiki,docs` puts them in one project — one membership, one
permission set; folders needing different access go in separate projects).
connects by id.) Skip only if the user already gave a project id or name.
- **Which folder syncs?** ALWAYS ask this one — where you were started
names a workspace, not what to sync, and an empty folder is not an
answer either. Hard rule: **never sync a repo root** — inside a repo,
knowledge always syncs as a mounted subfolder, or the root narrowed with
`--only`.
Whole-folder is only for a dedicated knowledge folder (a standalone
vault) that is the mount itself — and even then confirm it. Don't ask
open-endedly: scan the repo for existing knowledge folders (`wiki/`,
`docs/`, `notes/`, `handbook/`, an Obsidian vault — markdown-heavy,
not source code, whatever the name) and propose concrete choices:
lead with the candidates you found; when there are none, recommend
creating a dedicated subfolder (e.g. `wiki/`) as the default; plus a
different folder entirely (`bdrive init <path>`), or the whole folder
when it qualifies.
(`bdrive init . --only wiki,docs` puts them in one project — one
membership, one permission set; folders needing different access go in
separate projects).
Hard gate: do not run `bdrive init` until the user has answered the
folder question in this conversation — no exception for empty folders,
non-repos, or non-interactive sessions (if you cannot ask, end your
turn with the question instead of proceeding).
Ask with the **AskUserQuestion** tool (one question, header "Sync
folder", your recommendation first and labelled "(Recommended)", then
the alternatives) rather than plain prose — the user picks instead of
typing a path.
The mount is always exactly the folder you name: `bdrive init wiki
--project <p-id>` makes ./wiki the project. Nothing re-roots a mount
elsewhere. To sync only part of a folder, narrow it in place with
`--only`, which writes `.bdriveignore` rules and keeps the mount where
it is.
**Init first, git handoff second.** Init can refuse (this device may already
sync that project elsewhere); a refusal after `.gitignore` and `git rm
--cached` leaves the repo half-changed for nothing.
**One transport per folder.** If the chosen folder is currently git-tracked,
BearDrive and git would both write it — the silent-revert hazard. Get consent,
@@ -52,19 +76,20 @@ Obsidian, symlinks — in the beardrive skill's "Connecting knowledge tooling".)
Then run it non-interactively, e.g.:
```sh
bdrive init --name <project-name> --yes # dedicated knowledge folder
bdrive init --name <project-name> --shared wiki # in a repo: only ./wiki syncs
bdrive init --name <project-name> --shared wiki,docs # several shared subfolders, one project
bdrive init --name <project-name> --yes # this folder is the project
bdrive init wiki --name <project-name> --yes # ./wiki is the project
bdrive init . --name <project-name> --only wiki,docs --yes # narrow this folder to those subfolders
```
Re-running `bdrive init --yes` later is always safe: it resumes syncing
(including after the folder was renamed or moved). To add or remove shared
subfolders later, use `bdrive scope add <dir>` / `bdrive scope rm <dir>`
from the mount root — never hand-edit `.bdrive/config.json`.
(including after the folder was renamed or moved). To widen or narrow the
scope later, use `bdrive scope add <dir>` / `bdrive scope rm <dir>` from the
mount root — it edits the managed block in `.bdriveignore`, so nobody has to
hand-write negation rules.
After init, tell git what's what: add `.bdrive/` to `.gitignore` (per-machine
state, never committed). `.bdriveignore` always syncs through BearDrive
including on `--shared` mounts — so the team shares rules automatically;
committing it to git too is fine but optional.
state, never committed). `.bdriveignore` always syncs through BearDrive, and it carries the `--only`
scope rules too, so the team shares both automatically; committing it to git
as well is fine but optional.
## 4. Teach agents about the shared folder (ask first — never do this silently)
@@ -82,7 +107,7 @@ screen; it syncs to every member, so write it for the whole team, not
this machine.
**b. A root pointer in this repo (per machine, never synced).** For a
`--shared` mount inside a repo, append a short section to the repo root's
mount inside a repo, append a short section to the repo root's
`AGENTS.md` and/or `CLAUDE.md` — both if both exist; `AGENTS.md` is what
Codex and Hermes read (Codex never discovers nested instruction files,
and no platform knows the folder *matters* until told). Shape it like
@@ -108,52 +133,45 @@ standalone knowledge mount (dedicated folder, no enclosing repo) skip the
pointer: `AGENTS.md` at the mount root is loaded natively by every
platform.
## 5. Register agent sync hooks
## 5. Confirm the sync hooks
Run `bdrive hooks install` in the project. It detects the agent platforms
in use — Claude Code (`.claude/`), Codex (`.codex/`), Gemini CLI
(`.gemini/`), Hermes (`~/.hermes/`) — and idempotently merges beardrive's
sync hooks into each platform's own hook config, preserving any hooks
already there. Project-level files (`.claude/settings.json`,
`.codex/hooks.json`, `.gemini/settings.json`) ride the repo, so every
teammate gets them — plugin or not, whatever agent they use; Hermes hooks
are per-user (`~/.hermes/config.yaml`).
`bdrive init` already registered them — there is no separate hooks command to
run. It writes each platform's **user** config once per machine
(`~/.claude/settings.json`, `~/.codex/hooks.json`, `~/.gemini/settings.json`,
`~/.hermes/config.yaml`), because platforms read hook config only from the
directory a session starts in: a per-project file would fire only for sessions
that happen to start there, and — living inside a mount — would sync to the
whole team. Nothing agent-shaped is written into the project.
The registered hooks pull before every turn (the agent always reads the
team's latest files), push right after edits (artifacts land on the server
seconds after they're created — daemon or no daemon), and stamp every
change with the agent session that made it (`bdrive sync --note "<agent>
session <id>"` — visible in `bdrive log` and the hub's history views).
A third hook (`bdrive read-log`) queues which files the agent read — via
the native read tool, grep-style searches (the files the matches came
from), or shell commands that name project files — so the hub's read
heatmap can show admins what the team's agents actually consume. Reads
are reported on the next sync, never from the hook itself. They are fast no-ops in folders without
`.bdrive/`.
The hooks pull before every turn (the agent always reads the team's latest
files), push right after edits (artifacts land on the server seconds after
they're created — daemon or no daemon), and stamp every change with the agent
session that made it (`bdrive sync --note "<agent> session <id>"` — visible in
`bdrive log` and the hub's history views). A third hook (`bdrive read-log`)
queues which files the agent read — via the native read tool, grep-style
searches, or shell commands that name project files — so the hub's read
heatmap can show admins what the team's agents actually consume. Reads are
reported on the next sync, never from the hook itself. All of them are fast
no-ops outside bdrive folders.
Tell the user which platforms got hooks (`bdrive hooks` shows the status
table). If Codex is among them, mention they must run `/hooks` inside
Codex once to trust the project's `.codex` layer. To register a platform
that wasn't detected: `bdrive hooks install --agent claude,codex,gemini,hermes`.
Heads-up before installing: Hermes hooks are PER-USER (`~/.hermes/config.yaml`,
outside the repo) — mention that when it's among the targets, and skip it
unless the user actually uses Hermes.
Read init's output for the platforms it covered and tell the user. Pass on the
one manual step: **Codex hooks are experimental and off by default** — enable
with `[features] codex_hooks = true` in `~/.codex/config.toml`, and Codex asks
once to trust the hook definition. If a platform the user works with is
missing, `bdrive hooks install --agent <name>` adds it; `bdrive hooks` shows
the status table and `bdrive hooks uninstall` removes them.
If the user mentions teammates on other agents (Codex, Gemini CLI, Hermes),
tell them those teammates need no terminal either — they paste one prompt
into their own agent (the hub's project home page shows it filled in):
Teammates do not inherit hooks through the repo any more — each device
installs its own when it runs `bdrive init`. If the user mentions teammates on
other agents (Codex, Gemini CLI, Hermes), tell them those teammates need no
terminal either — they paste one prompt into their own agent (the hub's
project home page shows it filled in):
```
Follow https://raw.githubusercontent.com/runbear-io/beardrive/main/INSTALL_FOR_AGENTS.md
to connect this folder to BearDrive project <project-id> on <hub-url>.
to set up BearDrive project <project-id> on <hub-url>. Ask me which folder to sync.
```
The fetched instructions run install, skill install (which leaves the
beardrive skill in that agent's skills dir — `~/.codex/skills/beardrive/`
and friends — so later sessions are conversational), device-code login,
init, and hooks install. Handing teammates loose commands instead is how
the hooks step gets skipped.
## 6. Verify and summarize
Run `bdrive status` and confirm the daemon is running and pending is 0.
+13 -1
View File
@@ -34,6 +34,18 @@
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/beardrive-approve.sh\"",
"timeout": 10
}
]
}
]
}
}
}
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# PreToolUse: auto-approve beardrive's own setup commands so onboarding is not
# a permission gauntlet. Pure shell until the payload actually mentions bdrive,
# so the common case costs nothing; the binary makes the real decision and
# stays silent (= ask the user as usual) for anything it does not recognize.
payload=$(head -c 8192)
case "$payload" in
*bdrive*) ;;
*) exit 0 ;;
esac
command -v bdrive >/dev/null || exit 0
printf '%s' "$payload" | bdrive hook-approve 2>/dev/null || true
+36 -25
View File
@@ -13,14 +13,15 @@ Use this skill whenever the user is working with the `bdrive` CLI: initializing
| Action | Command |
|---|---|
| Start syncing a project (create/connect; the front door) | `bdrive init [<folder>]` — interactive on a TTY; flags `--name <x>` / `--project <id>` / `--shared <dirs>` (comma-separated or repeated) / `--yes` for scripts and agents (NEVER prompts without a TTY). Re-run to resume, including after the folder was renamed/moved. Runs the login flow first (against your hub URL) if the device has no session. |
| Start syncing a project (create/connect; the front door) | `bdrive init [<folder>]` — interactive on a TTY; flags `--name <x>` / `--project <id>` / `--server <hub-url>` (sign in there if this device has no session) / `--only <dirs>` (comma-separated) / `--yes` for scripts and agents. **The mount is always exactly the folder named**`bdrive init wiki` makes ./wiki the project, whose contents are the project's contents (NEVER prompts without a TTY). Re-run to resume, including after the folder was renamed/moved. Runs the login flow first if the device has no session, installs the beardrive skill, registers agent sync hooks in each platform's USER config (`--no-hooks` skips the hooks only), and prints the project's hub link — one command, so setup costs one permission prompt. |
| Run the daemon in the foreground | `bdrive init -f` |
| Stop syncing | `bdrive stop [<folder>]` — pauses daemon *and* agent hooks; `bdrive init` resumes (`--forget` also unregisters) |
| Show/change which subfolders sync | `bdrive scope` / `bdrive scope add <dirs...>` / `bdrive scope rm <dirs...>` — edits the include list set by `init --shared` (run from the mount root; NEVER hand-edit config.json for this). The daemon applies it within seconds. `rm` stops syncing a folder but deletes nothing, locally or on the hub; removing the last entry is refused (that would flip to whole-folder sync — use `bdrive stop` instead) |
| One sync cycle now | `bdrive sync [<folder>]``--note <text>` stamps session context; `--prune` also removes from the hub whatever `.bdriveignore` now excludes (see below); `--hook <label>` is the Claude turn-start hook's plumbing (event JSON in, sync + note, gated-link formula out) |
| Show/change which subfolders sync | `bdrive scope` / `bdrive scope add <dirs...>` / `bdrive scope rm <dirs...>` — edits the managed block of `.bdriveignore` rules that `init --only` writes (run from the mount root; never hand-write the negation syntax). The daemon applies it within seconds. `rm` stops syncing a folder but deletes nothing, locally or on the hub; removing the last entry is refused (that would flip to whole-folder sync — use `bdrive stop` instead) |
| One sync cycle now | `bdrive sync [<folder>]``--note <text>` stamps session context; `--prune` also removes from the hub whatever `.bdriveignore` now excludes (refuses when `.bdriveignore` narrows the scope with `!` rules — see below); `--hook <label>` is the Claude turn-start hook's plumbing (event JSON in, sync + note, gated-link formula out) |
| Stop syncing a path **and** take it off the hub | `bdrive forget <path>...` — appends the rule to `.bdriveignore` (trailing `/` for a directory) and prunes in the same run. **Deletes nothing on disk**, here or on teammates' devices: they receive the rule with the removal and just stop tracking the path. Idempotent; a path outside the project errors and writes nothing. This is the ONLY way to clean up something that synced before you excluded it — plain `.bdriveignore` edits and `bdrive scope rm` leave the hub's copy in place |
| Register agent sync hooks (Claude Code, Codex, Gemini CLI, Hermes) | `bdrive hooks install [<folder>]` — auto-detects the platforms in use and merges pull/push/session-note/read-tracking hooks into each one's own hook config, idempotently; bare `bdrive hooks` shows the status table |
| Register agent sync hooks (Claude Code, Codex, Gemini CLI, Hermes) | `bdrive hooks install` merges pull/push/session-note/read-tracking hooks into each platform's USER config (`~/.claude/settings.json` and friends), once per machine, idempotently. `bdrive init` runs it automatically, so this is mainly for retries or `--agent`-targeting an undetected platform; bare `bdrive hooks` shows the status table; `bdrive hooks uninstall` removes only our entries |
| Install this skill on another agent (Codex, Gemini CLI, Hermes, Claude Code) | `bdrive skill install [<folder>]` — writes the binary's own copy of this skill to each detected platform's user-level skills dir (`~/.codex/skills/beardrive/SKILL.md` and friends), idempotently; bare `bdrive skill` shows the status table. Then the user asks that agent to set the folder up and it runs `init` + `hooks install` itself |
| Auto-approve setup commands (hook plumbing) | `bdrive hook-approve` — the beardrive plugin's `PreToolUse` hook calls it so `bdrive init/login/hooks/status/sync/url` need no permission prompt. Narrow by design: a shell operator anywhere in the command disqualifies it |
| Record agent file reads (hook plumbing) | `bdrive read-log [<folder>]` — parses a hook event JSON from stdin and queues in-project reads locally (native reads, grep matches, and files named in shell commands); drained to the hub on the next sync as agent traffic in the read heatmap. Registered automatically by `bdrive hooks install`; rarely run by hand |
| Mounts + daemon + pending state | `bdrive status [<folder>]` |
| Change history | `bdrive log [<folder>] [-p path] [-n N]` |
@@ -30,7 +31,7 @@ Use this skill whenever the user is working with the `bdrive` CLI: initializing
| Sign this device out | `bdrive logout` — clears the saved token + account (folders untouched); `--forget` also drops the remembered server. The token is only cleared locally: the hub still accepts it and there is no revoke yet. |
| Link a synced file for teammates | `bdrive url <file>` — prints the file's hub viewer URL (sign-in + project membership required; always the latest content). Computed locally, no network; `--sync` pushes first so a just-created file's link resolves immediately; no arg = the project home page. **After creating a shareable artifact (.md/.html/.csv/report/plan) in the shared folder, include this link in your reply** so teammates can open it. |
| Share a synced file publicly by URL | `bdrive share <file>` — prints a link anyone can open (HTML renders as a page, markdown rendered, PDFs inline; sandboxed; always the latest content; no account needed). `--expires 24h` for self-destructing links; `--list` / `--revoke <token-or-url>` to manage. Put generated reports in the shared folder, sync, then share. |
| Set up a project for a Claude Code team | `/beardrive:install` — installs the CLI, signs in, runs init (whole/shared folder), offers the two-file agent orientation (synced `<shared>/AGENTS.md` map + repo-root pointer), and registers agent sync hooks via `bdrive hooks install` (pull at turn start, push after edits, session-note stamping — for every detected platform, not just Claude) |
| Set up a project for a Claude Code team | `/beardrive:install` — installs the CLI, signs in, runs init (whole/shared folder), offers the two-file agent orientation (synced `<shared>/AGENTS.md` map + repo-root pointer), and registers agent sync hooks as part of `bdrive init` (pull at turn start, push after edits, session-note stamping — for every detected platform, not just Claude) |
| Per-file / folder change history in the web UI | History button (file versions or project feed) and per-folder ⌚ — each entry: account, time, device (name/OS/IP), view/download of that exact version. API: `GET /api/p/<id>/history?path=\|prefix=`, `GET /api/p/<id>/blob?sha=` |
| Web server: viewer + multi-project sync hub (read-only unless `--upload`) | `bdrive web [<folder> \| <storage-root-url>]` (serves cwd by default, `--addr :4173`; `-c config.json` reads remote/addr/upload/projects_db/database/auth settings from a file, explicit flags win; a storage root URL makes it a hub hosting many projects at `<root>/<project-id>/`, registry in `--projects-db` file, default `$BDRIVE_HOME/projects.json`; `--upload` lets browsers add files, client devices push, and projects be created — direct to storage via expiring presigned URLs on S3/GCS, relayed through the server for `file://`; `--upload-ttl 15m`; clients never see the remote URL or credentials; hub projects are walled by org membership — invite teammates from the web UI; the viewer has a ⌘K palette for fuzzy file search, project switching, and quick actions) |
@@ -41,7 +42,7 @@ Use this skill whenever the user is working with the `bdrive` CLI: initializing
Two files at the mount root control a folder's sync behavior:
- **`.bdrive/`** — the folder's settings **directory**; `config.json` inside holds the **stable mount id** (`m-xxxxxxxx`) plus `volume`, `remote`, optional `include`. Written by `bdrive init`; safe to hand-edit (a running daemon picks changes up on its next tick). It is **never synced**, holds **no credentials** (the token lives in `~/.bdrive/settings.json`), and because all state is keyed by the mount id — not the path — the folder can be **renamed or moved freely**; the daemon exits on a move and the next bdrive command at the new location resumes.
- **`.bdriveignore`** — opt-out list, gitignore-style. It **always syncs** — even on a `--shared <dir>` mount where it sits outside the include list, and even if a rule matches it — so all devices share the same rules. Syntax subset: `#` comments, `*` within a segment, `**` across segments, `?`, trailing `/` for directories-only, a `/` elsewhere anchors to the mount root, `!` re-includes.
- **`.bdriveignore`** — opt-out list, gitignore-style. It **always syncs** — even when a rule matches it — so all devices share the same rules. It also carries the sync scope: `init --only` writes a managed `# bdrive scope` block of negation rules here (`/*` then `!/wiki/`), so scope is one mechanism, shared with the team, not a per-device setting. Syntax subset: `#` comments, `*` within a segment, `**` across segments, `?`, trailing `/` for directories-only, a `/` elsewhere anchors to the mount root, `!` re-includes.
```jsonc
// .bdrive/config.json
@@ -49,7 +50,8 @@ Two files at the mount root control a folder's sync behavior:
"id": "m-5a10b713",
"volume": "agent-workspace",
"remote": "https://drive.example.com/p/p-7f3a2c91",
"include": ["/shared/"] // optional: sync ONLY these (init --shared; edit with bdrive scope add/rm)
// legacy: mounts created before the scope moved into .bdriveignore may
// carry "include": ["/shared/"] — still honored, never written now
}
```
@@ -67,7 +69,7 @@ Selective-sync semantics — important when advising users:
- Adding a pattern for an already-synced file makes this device **stop tracking it without deleting it anywhere** — the file stays on disk locally and on every other device. Deleting it locally after that does not propagate either.
- Because `.bdriveignore` syncs, adding a rule on one device applies it everywhere on the next cycle.
- Stopping tracking is not cleanup: **the hub keeps everything that synced before the rule existed**. `bdrive forget <path>` (or `bdrive sync --prune`) removes it from the hub while leaving every device's disk untouched. Reach for it whenever a user says a file "should not be up there".
- Prune reconciles against `.bdriveignore` **only**, never against this device's `include` scope — ignore rules are shared, the scope is per-device, and a narrow scope means "not on my disk", not "not on the hub". To clean up something the scope excludes (or a leak into it), `bdrive forget` it, which writes the exclusion into the shared rules first.
- Prune reconciles against `.bdriveignore`, which is **shared** — so it REFUSES outright when those rules narrow the scope with `!` (the managed `# bdrive scope` block that `init --only` / `bdrive scope` write): pruning there would delete everything outside the scope from the hub, for the whole team. Use `bdrive forget <path>` for a specific path — it writes the exclusion into the shared rules first. A *legacy* `include` list in `.bdrive/config.json` is the one per-device case, and prune never reconciles against it ("not on my disk", not "not on the hub").
- Nothing is destroyed by a prune: it is an ordinary journaled delete, so it shows in `bdrive log` and every past version stays in the hub's history. If a teammate edits the file in the window before they sync, their version wins and the path returns — run `--prune` again once they have synced.
---
@@ -77,7 +79,7 @@ Selective-sync semantics — important when advising users:
### Init flow
1. Sign-in happens lazily: `bdrive init` runs the login flow itself when the device has no session, so don't ask users to sign up ahead of time. Bare `bdrive login` targets BearDrive Cloud (beardrive.ai) — signing up in the browser auto-creates a free personal workspace; a pending team invite routes them into that team instead. Self-hosting teams: `bdrive login https://your-hub`.
2. Pick what to sync BEFORE running init. In a repo, look for existing knowledge folders (`wiki/`, `docs/`, `notes/`, `handbook/`, an Obsidian vault) and propose them as `--shared <dirs>` — several folders can share one project (`--shared wiki,docs`) when the same people should see all of them; folders needing different access belong in separate projects. Confirm, don't interrogate. Never sync a repo root. Then run `bdrive init` in the folder. Interactive on a TTY (create new / connect existing project; whole folder / shared subfolder); with flags or without a TTY it creates-or-joins a project named after the folder and syncs everything. It:
2. Pick what to sync BEFORE running init. In a repo, look for existing knowledge folders (`wiki/`, `docs/`, `notes/`, `handbook/`, an Obsidian vault) and propose mounting one of them (`bdrive init wiki`) or, when several folders belong to the same project and the same people, mount their parent and narrow it: `bdrive init . --only wiki,docs`. Folders needing different access belong in separate projects. Confirm, don't interrogate. Never sync a repo root without `--only`. Then run init. Interactive on a TTY (create new / connect existing project; whole folder / only some subfolders); with flags or without a TTY it creates-or-joins a project named after the folder and syncs everything. It:
- writes `<folder>/.bdrive/config.json` (mount id + project + remote) and registers the mount id in `~/.bdrive/mounts.json`,
- seeds a starter `.bdriveignore` (node_modules, build dirs, caches, `.env*`) when none exists,
- opens the volume store under `~/.bdrive/volumes/<mount-id>/`,
@@ -89,7 +91,7 @@ Selective-sync semantics — important when advising users:
- `--name <x>` — project name to create-or-join (default: folder basename).
- `--project <id>` — connect an existing project by id (`p-xxxxxxxx`).
- `--shared <dirs>` — sync only these subfolders (repeatable or comma-separated: `--shared wiki,docs`; becomes the include list; remote paths keep the prefix so all devices see the same layout).
- `--only <dirs>` — sync only these subfolders of the mount (comma-separated: `--only wiki,docs`). Writes `.bdriveignore` scope rules; the mount root is still the folder you named, so remote paths keep the `wiki/` prefix and every device sees the same layout.
- `--yes, -y` — accept defaults, never prompt.
- `--foreground, -f` — run the daemon in the foreground (systemd/launchd/containers).
@@ -141,13 +143,22 @@ conflict-copy ops keep their own `conflict copy of <path>` note.
### Agent sync hooks (Claude Code, Codex, Gemini CLI, Hermes)
`bdrive hooks install [<folder>]` registers turn-boundary sync for every
agent platform it detects (by config dir, in the project or home):
agent platform it detects (by config dir, in the project or home).
`bdrive init` runs the same registration automatically (skip with
`--no-hooks`), so a plain init already covers hooks:
**Hooks are user-level, once per machine.** Every platform reads hook config
only from the directory a session started in — never a parent, never a
subfolder — so a per-project file would cover only sessions that happen to
start there, and inside a mount it would sync to the whole team. BearDrive
therefore writes each platform's own user config and nothing inside a
project. Sync still happens without hooks: the daemon's loop is independent.
| Platform | Config it writes | Pull / push / read events |
|---|---|---|
| Claude Code (& Cowork) | `<project>/.claude/settings.json` | `UserPromptSubmit` (pull + injects the gated-link formula) / `PostToolUse` (Write\|Edit) / `PostToolUse` (Read\|Grep\|Bash) |
| Codex (ChatGPT) | `<project>/.codex/hooks.json` | `UserPromptSubmit` / `PostToolUse` (apply_patch) / `PostToolUse` (read_file\|shell, best-effort) — user must `/hooks`-trust the layer once |
| Gemini CLI | `<project>/.gemini/settings.json` | `BeforeAgent` / `AfterTool` (write_file\|replace) / `AfterTool` (read_file\|read_many_files\|search\|shell) |
| Claude Code (& Cowork) | `~/.claude/settings.json` | `UserPromptSubmit` (pull + injects the gated-link formula) / `PostToolUse` (Write\|Edit\|MultiEdit) / `PostToolUse` (Read\|Grep\|Bash) |
| Codex (ChatGPT) | `~/.codex/hooks.json` | `UserPromptSubmit` / `PostToolUse` (apply_patch) / `PostToolUse` (read_file\|shell, best-effort); experimental and off by default (`[features] codex_hooks = true` in `~/.codex/config.toml`), and Codex asks once to trust the hook |
| Gemini CLI | `~/.gemini/settings.json` | `BeforeAgent` / `AfterTool` (write_file\|replace\|edit) / `AfterTool` (read_file\|read_many_files\|search_file_content\|run_shell_command) |
| Hermes | `~/.hermes/config.yaml` (per-user) | `pre_llm_call` / `post_tool_call` (write_file\|patch) / `post_tool_call` (read_file\|grep\|bash) |
Every platform pipes hook JSON with a `session_id`, so one hook command
@@ -164,12 +175,12 @@ missing group on re-install, and a registered hook's matcher is upgraded
in place when coverage grows (re-run `bdrive hooks install` after upgrading
the binary); `--agent claude,codex,gemini,hermes` overrides
detection; bare `bdrive hooks` prints the detection/registration table.
Project-level configs ride the repo, so hooks reach the whole team.
Each device registers its own when it runs `bdrive init` — nothing rides the repo.
**When a teammate is setting up on a non-Claude agent**, point them at
`bdrive skill install` (see below) rather than a list of commands: the
agent then runs `init` + `hooks install` itself, which is exactly the step
hand-copied setups miss.
agent then runs `init` itself which also registers the hooks, exactly
the step hand-copied setups miss.
### Installing this skill on other agents
@@ -189,11 +200,11 @@ agent has no BearDrive knowledge yet:
```
Follow https://raw.githubusercontent.com/runbear-io/beardrive/main/INSTALL_FOR_AGENTS.md
to connect this folder to BearDrive project <project-id> on <hub-url>.
to set up BearDrive project <project-id> on <hub-url>. Ask me which folder to sync.
```
The fetched instructions cover install, `bdrive skill install`, sign-in,
`bdrive init --project`, and `bdrive hooks install`. They use `login
The fetched instructions cover install and one `bdrive init --project
--server`, which signs in, installs the skill and registers the hooks. Sign-in uses `login
--device` because an agent is driving: a browser-callback sign-in is
invisible to it mid-turn, while the device flow yields a code and URL it can
hand back in chat. The hub's project home page renders this prompt with the
@@ -225,7 +236,7 @@ cd ~/agent-workspace && bdrive init
bdrive init ~/agent-workspace --name agent-workspace --yes
# Only share a subfolder
bdrive init ./research --shared shared
bdrive init ./research/shared
# Pause syncing for the day
bdrive stop ~/agent-workspace
@@ -239,7 +250,7 @@ bdrive stop ./notes --forget
When guiding `bdrive init`, detect existing knowledge tooling and connect it instead of blind-syncing the folder. Two rules govern every case:
- **One transport per folder.** Never sync a folder that has another writer. Git-tracked paths: a teammate's `git pull` or branch switch rewrites files with older content, and sync broadcasts that as a fresh edit — silently reverting the team's latest pages. A gbrain brain root: every private capture and overnight enrichment would become team-visible, and each member's cron rewriting the same pages fills the project with conflict copies. Moving a folder from git to beardrive is a **handoff**: `git rm -r --cached <dir>` + add `<dir>/` to `.gitignore`, stage the change but let the user commit it (teammates then pull and re-init; identical content converges with no conflicts). If they want a git record anyway, offer **one-way snapshots** (a scheduled job commits the synced folder's state to an archive branch — git only ever reads the folder) and note that hub history (`bdrive log -p <path>`) usually covers the need.
- **Knowledge syncs as a scoped folder.** Inside a repo, always `--shared <dirs>` (one or more subfolders) — never the repo root. A dedicated knowledge folder (an empty dir, a standalone vault) may be the mount itself. The sync scope is per-device (`.bdrive/` never syncs), so recommend the same `--shared <dirs>` when each teammate connects.
- **Knowledge syncs as a scoped folder.** Inside a repo, mount the knowledge subfolder itself (`bdrive init wiki`) — never the bare repo root. When several subfolders belong to one project, mount the repo root with `--only wiki,docs`; the scope rules land in the synced `.bdriveignore`, so every teammate inherits the same scope automatically. A dedicated knowledge folder (an empty dir, a standalone vault) may be the mount itself.
Detection ladder — first match wins; if two rungs match, ask which to connect:
@@ -251,20 +262,20 @@ Detection ladder — first match wins; if two rungs match, ask which to connect:
- gbrain's sync cron keeps re-indexing what beardrive pulls in; suggest switching it to `gbrain sync --no-pull` (ask before editing a crontab). On PGLite, remind: stop `gbrain serve` before large syncs (single-writer contention).
- **One enrichment owner per shared folder.** Exactly one gbrain instance may write enrichment into the shared source — prefer a dedicated bot account/device (hub-side if possible) over a member's laptop; every other member indexes the source read-only with enrichment off for it. Members still capture: route each member's automated writes (email ingestion, meeting notes) into their own subtree (e.g. `inbox/<member>/`) so no path ever has two writers, and let the owner fold captures into canonical pages. Nothing enforces the election, so a `*.bdrive-conflict-*` file naming the owner's device is the canary that a second enricher (or a human race) appeared — surface it, don't ignore it.
- **Owner's cycle discipline**: sync (pull) → `gbrain sync` (import human edits into the DB) → enrich → write back promptly, skipping files modified in the last few minutes and `*.bdrive-conflict-*` files. A write-back from a stale DB copy after a pull is causally "later," so it silently reverts the human's edit with **no conflict copy** — re-check the file changed since import before writing. Keep enrichment output deterministic (stable ordering, no embedded timestamps): byte-identical rewrites produce no op at all, so idempotent passes are free.
2. **OKF** — markdown with OKF v0.1 frontmatter (confirm with `openknowledge validate` if the CLI is present; don't install just to detect). Offer: (a) connect the wiki dir via `--shared` — with the git handoff if tracked — or (b) keep the wiki PR-gated in git and create a new shared folder (starting-point menu below). Recommend (a) when the wiki is the team's knowledge, (b) when it's review-gated repo documentation.
2. **OKF** — markdown with OKF v0.1 frontmatter (confirm with `openknowledge validate` if the CLI is present; don't install just to detect). Offer: (a) mount the wiki dir (`bdrive init <dir>`) — with the git handoff if tracked — or (b) keep the wiki PR-gated in git and create a new shared folder (starting-point menu below). Recommend (a) when the wiki is the team's knowledge, (b) when it's review-gated repo documentation.
3. **Wiki-ish folder** — a markdown-dense dir named `docs`/`wiki`/`notes`/`kb` with no knowledge tooling. Check `git log -- <dir>`: dormant → recommend connecting it as the live team space (handoff included); active PR traffic → recommend a new shared folder instead, and say why. After connecting, offer — as a separate consent, it rewrites their files — an in-place upgrade to OKF (`openknowledge from <dir>`) for validation and agent-readability.
4. **Nothing** — empty or unstructured folder: offer a starting point, in this order: **(a) OKF (recommended** — open spec, plain files, zero runtime, upgradeable to gbrain later**)**, (b) gbrain (full agent brain; heavier — per-member local DB), (c) blank, (d) describe-it (user describes the purpose; scaffold a custom OKF shape, redirecting to gbrain if the description is graph-shaped: entities, relationships, "who/what" queries).
Conflict copies are named `<file>.bdrive-conflict-<device>-<timestamp>` and sync like normal files. `openknowledge validate` does **not** flag them (they aren't `.md`) — pair validate with a `*.bdrive-conflict-*` glob check when offering a post-edit validation hook.
Every branch ends the same way: verify (`bdrive status`, pending 0), the consent-gated two-file agent orientation (next section), and the teammate onboarding sentence (invite link → `bdrive init`same `--shared` scope).
Every branch ends the same way: verify (`bdrive status`, pending 0), the consent-gated two-file agent orientation (next section), and the teammate onboarding sentence (invite link → `bdrive init`the scope rides `.bdriveignore`, so it matches automatically).
### Teaching agents the folder (AGENTS.md)
A newly mounted shared folder is hundreds of opaque files to an agent. Two files with different jobs fix that — offer each as its own consent, never write either silently:
1. **The folder's own map — `<shared>/AGENTS.md` (synced, team-wide).** The single source of truth for conventions: what each area is for, naming patterns, where agents should *write* (e.g. reports → `reports/`), what not to touch. Because the folder syncs, the map travels with it — every member on every platform gets it, and hub history tracks who changed the rules. It is scaffolded **once, by the project creator** (explore the folder, draft it, keep it under a screen); joiners read it and follow it — a new member's agent must not rewrite team conventions on day one.
2. **A root pointer (per machine, never synced).** For `--shared` mounts inside a repo, append 23 lines to the repo root's `AGENTS.md` and/or `CLAUDE.md` (both if both exist): the folder is shared via BearDrive, read `<shared>/AGENTS.md` before working there, put shareable artifacts there, no secrets. Point at the synced map — don't duplicate its conventions, or the copy goes stale.
2. **A root pointer (per machine, never synced).** For mounts inside a repo, append 23 lines to the repo root's `AGENTS.md` and/or `CLAUDE.md` (both if both exist): the folder is shared via BearDrive, read `<shared>/AGENTS.md` before working there, put shareable artifacts there, no secrets. Point at the synced map — don't duplicate its conventions, or the copy goes stale.
The pointer is not optional politeness — platform discovery differs: