From 31f705e287e27b248b56485a6de4a919626fec04 Mon Sep 17 00:00:00 2001 From: "Snow W. Lee (Sungwon)" Date: Thu, 30 Jul 2026 12:40:47 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20drop=20the=20Claude=20plugin=20and=20t?= =?UTF-8?q?he=20bundled=20skill=20=E2=80=94=20hooks=20are=20the=20integrat?= =?UTF-8?q?ion=20(#85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two front doors for the same setup, and one of them was a second copy of everything. The plugin shipped the skill the CLI already installs (to four platforms, not one), hooks that ran the identical commands `bdrive hooks install` writes machine-level, and an install flow duplicating INSTALL_FOR_AGENTS.md. Nothing deduped, so a machine with both pulled twice per turn — two blocking syncs — and showed two identical `beardrive` skills in the picker. What remains is `internal/agenthooks` plus the runbook: init registers a blocking pull (which also injects the gated-link convention as additionalContext), an async push on Write/Edit, and read-log for the heatmap, in each platform's user config, once per machine. That is the whole integration, and it is the part that was never optional. Removed: plugin/, .claude-plugin/marketplace.json, internal/agentskills, `bdrive skill`, `bdrive hook-approve` (its PreToolUse auto-approve only ever helped when a plugin pre-installed it; the substitute is a `Bash(bdrive:*)` permission entry, which is user-owned config and needs no code). The e2e now asserts the absence: no SKILL.md in any platform's skills dir after init, and no `skill` subcommand. login_test keeps the "no revoke surface" wording check on logoutNote alone. Claude-Session: https://claude.ai/code/session_016aYntCWwdUhpzUfEk3ddyJ Co-authored-by: Claude Opus 5 --- .claude-plugin/marketplace.json | 24 - .claude/skills/beardrive | 1 - .claude/skills/onboarding-e2e/SKILL.md | 12 +- CLAUDE.md | 26 +- INSTALL_FOR_AGENTS.md | 44 +- README.md | 109 ++-- architecture/README.md | 2 +- architecture/cli-sync.md | 14 +- architecture/overview.md | 2 - cmd/bdrive/approve.go | 89 --- cmd/bdrive/init.go | 22 - cmd/bdrive/init_test.go | 30 - cmd/bdrive/login_test.go | 30 +- cmd/bdrive/main.go | 2 - cmd/bdrive/skill.go | 87 --- internal/agentskills/agentskills.go | 123 ---- internal/agentskills/agentskills_test.go | 100 --- internal/webapp/cli_e2e_test.go | 12 + .../frontend/src/components/ConnectGuide.tsx | 6 +- .../{index-CuzT86iF.js => index-BCH1-K2y.js} | 2 +- internal/webapp/static/index.html | 2 +- plugin/.claude-plugin/plugin.json | 21 - plugin/commands/init.md | 92 --- plugin/commands/install.md | 184 ------ plugin/commands/status.md | 13 - plugin/embed.go | 14 - plugin/hooks/hooks.json | 51 -- plugin/scripts/beardrive-approve.sh | 12 - plugin/scripts/beardrive-pull.sh | 11 - plugin/scripts/beardrive-read.sh | 11 - plugin/scripts/beardrive-sync.sh | 23 - plugin/skills/beardrive/SKILL.md | 583 ------------------ web/docs/astro.config.mjs | 3 +- .../manual/{skills-and-hooks.md => hooks.md} | 38 +- web/docs/src/content/docs/manual/install.md | 5 +- .../src/content/docs/manual/setup-by-hand.md | 10 +- web/docs/src/content/docs/reference/cli.md | 12 +- .../content/docs/reference/project-files.md | 2 +- web/docs/src/content/docs/start/setup.md | 71 +-- .../content/docs/use-cases/multi-device.md | 15 +- .../content/docs/use-cases/team-artifacts.md | 7 +- 41 files changed, 144 insertions(+), 1773 deletions(-) delete mode 100644 .claude-plugin/marketplace.json delete mode 120000 .claude/skills/beardrive delete mode 100644 cmd/bdrive/approve.go delete mode 100644 cmd/bdrive/skill.go delete mode 100644 internal/agentskills/agentskills.go delete mode 100644 internal/agentskills/agentskills_test.go rename internal/webapp/static/assets/{index-CuzT86iF.js => index-BCH1-K2y.js} (99%) delete mode 100644 plugin/.claude-plugin/plugin.json delete mode 100644 plugin/commands/init.md delete mode 100644 plugin/commands/install.md delete mode 100644 plugin/commands/status.md delete mode 100644 plugin/embed.go delete mode 100644 plugin/hooks/hooks.json delete mode 100755 plugin/scripts/beardrive-approve.sh delete mode 100755 plugin/scripts/beardrive-pull.sh delete mode 100755 plugin/scripts/beardrive-read.sh delete mode 100755 plugin/scripts/beardrive-sync.sh delete mode 100644 plugin/skills/beardrive/SKILL.md rename web/docs/src/content/docs/manual/{skills-and-hooks.md => hooks.md} (70%) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json deleted file mode 100644 index 55bd1e8..0000000 --- a/.claude-plugin/marketplace.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "beardrive", - "owner": { - "name": "runbear", - "email": "snow@runbear.io" - }, - "description": "BearDrive — the open-source Google Drive for AI agents: one folder your team and their agents share, with per-change attribution and read analytics.", - "plugins": [ - { - "name": "beardrive", - "source": "./plugin", - "description": "BearDrive: folders that stay in sync across your team and agents through a self-hostable BearDrive hub, with per-file history and public share links. Installs the beardrive skill, /beardrive:install, /beardrive:init and /beardrive:status commands, and turn-boundary sync hooks in one step.", - "category": "workflow", - "tags": [ - "sync", - "files", - "workspace", - "agents", - "memory", - "claude-code" - ] - } - ] -} diff --git a/.claude/skills/beardrive b/.claude/skills/beardrive deleted file mode 120000 index f341deb..0000000 --- a/.claude/skills/beardrive +++ /dev/null @@ -1 +0,0 @@ -../../plugin/skills/beardrive \ No newline at end of file diff --git a/.claude/skills/onboarding-e2e/SKILL.md b/.claude/skills/onboarding-e2e/SKILL.md index 70bb950..440843e 100644 --- a/.claude/skills/onboarding-e2e/SKILL.md +++ b/.claude/skills/onboarding-e2e/SKILL.md @@ -1,6 +1,6 @@ --- name: onboarding-e2e -description: "Live end-to-end test of BearDrive's agent-first onboarding: run the real paste-prompt flow in a fresh headless Claude session (and optionally a full role-played user conversation) against a seeded local hub, asserting the scope hard-gate, hooks-via-init, and plugin-upgrade behaviors, and report transcripts plus doc-vs-reality findings. Self-bootstrapping — no inputs needed. Use when INSTALL_FOR_AGENTS.md, plugin/commands/install.md, the CLI init/login/hooks flow, or hub auth changed. Args: [hub-url] [authenticated BDRIVE_HOME] [bdrive-binary] (all optional)" +description: "Live end-to-end test of BearDrive's agent-first onboarding: run the real paste-prompt flow in a fresh headless Claude session (and optionally a full role-played user conversation) against a seeded local hub, asserting the scope hard-gate and hooks-via-init behaviors, and report transcripts plus doc-vs-reality findings. Self-bootstrapping — no inputs needed. Use when INSTALL_FOR_AGENTS.md, the CLI init/login/hooks flow, or hub auth changed. Args: [hub-url] [authenticated BDRIVE_HOME] [bdrive-binary] (all optional)" --- # Agent-onboarding live E2E @@ -68,9 +68,9 @@ must be *offered*, and denied if attempted). whole folder"` (same env, same cwd). Now init must run and its output must show hooks registered *inline* — no separate `bdrive hooks install` invocation anywhere in the transcript. -3. **Plugin upgrade**: for an already-installed plugin the agent offers - `claude plugin marketplace update beardrive` + `claude plugin update - beardrive@beardrive` — offered to the user, never silently run. +3. **No plugin, no skill**: the transcript must not try to install a + Claude plugin, a marketplace, or a `SKILL.md` — the hooks that `init` + registers are the whole integration. 4. **Payoff**: the final message hands a hub link; verify it serves real content with an authenticated `curl -b $jar`. @@ -167,5 +167,5 @@ even if the run "worked". "ok (cached)" and no server. - `command -v bdrive` may find a Homebrew binary older than the tree under test — always prepend `$W` to PATH and watch for version skew. -- The agent's `bdrive skill install` writes to the real `~/.codex`, - `~/.gemini`, `~/.hermes` skill dirs (user-level); harmless but expect it. +- `bdrive init` writes hooks into the real `~/.claude/settings.json` (and + friends) — user-level, idempotent, but expect it on the test machine. diff --git a/CLAUDE.md b/CLAUDE.md index 4cae9ac..b8fc7a5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,7 +46,7 @@ Package roles (`internal/`): - **`config`** — global state under `$BDRIVE_HOME` (default `~/.bdrive`): device identity (`device.json`), settings (`settings.json`: default server + device token + signed-in account), and the mount registry (`mounts.json`, keyed by **stable mount id**, holding only each mount's last-known path). The per-folder `.bdrive/` directory (`project.go`) holds `config.json` with the mount id + volume/remote/include; **nothing is keyed by the folder path**, so renames/moves are free — `ResolveMount` self-heals the registry path, and the volume store lives at `~/.bdrive/volumes//`. `.bdrive/` is never synced and holds no credentials. - **`webapp`** — the `bdrive web` server, in two modes. Single-volume: `Source` is a `DirSource` (plain folder from disk) or `RemoteSource` (folds journals into a file tree with per-file provenance). Hub: `Root` + `Projects` host many projects on one storage root, each under `//` via `remote.Prefixed`; `ProjectDB` (`projects.go`) is a file-backed registry (JSON, loaded at open, rewritten atomically per change) with create-or-join-by-name semantics, name-scoped per organization. Orgs (`orgs.go`, file-backed `orgs.json`) wall projects by membership (email → owner|member): every per-project route — viewer APIs, uploads, history, shares management, the `/store/*` sync proxy — 403s for non-members, `/api/projects` lists only your orgs' projects, owners mint expiring multi-use invite links (`/join/`), and a pre-org hub migrates all projects into a "default" org (all existing accounts join, oldest owns) at startup. `QuotaProvider` (`quota.go`) is the plan-enforcement seam mirroring `AuthProvider` — CheckWrite/RecordUsage on every write path, CheckSeat on invite redemption; OSS ships only `UnlimitedQuota`, managed deployments swap the provider. Renders markdown (goldmark + Obsidian `[[wikilinks]]`). With `--upload` it accepts writes: browser uploads (`upload.go` — direct-to-storage via presigned URLs when the backend implements `remote.PutSigner`, relayed otherwise; ops journaled under the server's own device) and the per-project `/api/p//store/*` proxy (`store.go`) that whole devices sync through — the `https://` remote backend (`remote/http.go`) is its client; journals are never presigned, only immutable blobs. Frontend is a React + TypeScript app (`webapp/frontend/`, Vite + Tailwind v4 + shadcn/ui — Radix-based components copied into `src/components/ui`, themed from the BearDrive tokens in `src/tw.css`; TanStack query/table/virtual, react-hook-form + zod, cmdk, sonner, lucide-react) whose **built output is committed** at `webapp/static/` — the `go:embed static` target — so plain `go build` needs no Node; after any `frontend/src` change run `npm run build` there and commit the new `static/` (`frontend/check-dist.sh` verifies freshness; e2e suite: `npm run e2e` — Playwright against the seeded harness in `e2e_serve_test.go`, port 8993). It learns everything from `/api/config` (+ `/api/projects` in hub mode) and never sees storage info or credentials. It uses native History-API path routing (`//` in hub mode, `/` in volume mode, `/join/` for invites — no `#`, slashes stay literal). **Every user-facing page owns a URL path**: new surfaces are view routes (`//{dashboard|history|install|settings}[/]`, `VIEW_ROUTES` in `router.ts`; renamed segments live in `LEGACY_VIEWS` and are normalized away on arrival) so deep links, reload, and back/forward always work — never URL-less panel state (the org/hub admin panels are the legacy exceptions; don't add more) implemented by the in-repo synchronous router `frontend/src/nav.ts` + `router.ts` (deliberately NOT a router library: react-router v7's startTransition navigation left stale views on screen); `Server.frontend` serves `index.html` as the SPA fallback for any non-asset, non-API/auth/share route so deep links and refreshes resolve (hashed `assets/*` are cached immutable, everything else no-cache), and all client API/asset URLs are root-absolute so a deep path doesn't break relative resolution. Rendered markdown is transformed as a string before mounting and link clicks are delegated on the container — never patch the `dangerouslySetInnerHTML` subtree after commit (React re-applies the markup on unrelated updates and discards DOM patches). **Read heat** (`reads.go`): a `ReadLedger` (hub-only, nil = off, config `reads` block) aggregates read telemetry into daily per-actor buckets, debounced to 10-minute visits, folded into all-time rows past `retention_days` — viewer file/render/download = human (recorded via the project id the `proj()` resolver stashes in the request context), `/s/*` hits = share, device-reported reads (`POST /api/p//reads`) = agent; `/store/*` replication and history `/blob` views are NEVER reads. `GET /api/p//heat?prefix=&days=` returns counts/distinct-readers/last-read only — actor identities (the email/device/token in the buckets) must never appear in an API response. Recording and flushing degrade silently (log once); telemetry must never fail a request or a sync cycle. The frontend shows heat dots on folder listings and a per-project Dashboard quadrant (reads × staleness, route `//dashboard`) — both visible to every project member, since `/heat` is membership-gated and identity-free. **Hub metadata persistence** (accounts, projects, orgs+invites, shares, devices, read buckets — never blobs or journals) sits behind a pluggable `MetaStore` of typed repos (`db.go`): the service structs (`BuiltinAuth`, `OrgDB`, `ProjectDB`, `ShareDB`, `DeviceRegistry`, `ReadLedger`) keep their in-memory maps + logic and persist each change as one record through a repo (the `ReadRepo` alone is batch-oriented — one flush, one write). Two backends — `db_file.go` (the historical JSON files, still the zero-dep default, reached via the `Open*(path)` constructors) and `db_sql.go` (one `database/sql` impl over pure-Go drivers: `modernc.org/sqlite` locally, `jackc/pgx` for Postgres/Supabase, portable schema + idempotent migrations + transactional multi-row writes). `web.go`'s `database` config (`{driver:file|sqlite|postgres, dsn}`) selects it; file is default and untouched. `db_conformance_test.go` runs the same service ops against every backend. -`cmd/bdrive/` is a thin cobra CLI over these packages (`login`, `logout`, `init`, `stop`, `sync`, `scope`, `forget`, `status`, `log`, `share`, `export`, `import`, `url`, `hooks`, `skill`, `read-log`, `hook-approve`, `web`, `whoami`, `daemon`, `version` — `mnt`/`umnt`/`remote` are gone; `init` is the front door and `stop` pauses). `export`/`import` (`migrate.go`) move a whole project between hubs with full fidelity: the archive is the remote store layout (all devices' journals + all blobs) in a tar.gz, streamed through the existing `remote.Backend` — no server-side support needed, so it works against any hub in either direction (the anti-lock-in story for cloud-hesitant users). `bdrive login` signs the device in (bare form uses the remembered server or `config.DefaultServer` = beardrive.ai; loopback-callback browser flow in `login.go`, `--device` for headless) and stores server+token+account in `settings.json`; `bdrive logout` clears the saved token+account (keeps the remembered server unless `--forget`). Switching hubs is `bdrive login ` then re-`init` — `init` is the only thing that writes a folder's remote (always a hub, `server + "/p/" + id`); there is no client command to point a folder at a raw bucket. `bdrive init` is interactive on a TTY (survey menus: create-new vs connect-existing with a project list; whole-folder vs only-some-subfolders) with full flag bypass (`--name/--project/--only/--yes`) and never prompts without a TTY; it runs the login flow first when there is no session, writes `.bdrive/config.json`, seeds `.bdriveignore`, registers agent sync hooks in each platform's USER config (`~/.claude/settings.json` and friends — once per machine, never inside a project: platforms read hook config only from the directory a session starts in, so a per-project file covers only sessions that start there and, living in a mount, would sync to the team; `Install` also migrates away hooks older versions wrote into projects) and installs the beardrive skill, and starts sync via `startSync`; re-running it resumes — including after a folder move. **A mount is always exactly the folder named** — there is no re-rooting flag. Syncing only part of a mount is `--only wiki,docs`, which writes a bdrive-managed block of `.bdriveignore` negation rules (`cmd/bdrive/scopefile.go`; `bdrive scope add/rm` edits the same block) rather than a second scope mechanism: the old `Include` list in `config.json` is legacy — still honored, never written. Because the rules live in the synced `.bdriveignore`, scope is team-wide, which is why `sync --prune` refuses when `!` rules are present (it would strip everything outside the scope from the hub for everyone; `bdrive forget ` is the per-path tool). `init` also refuses a second folder for a project this device already syncs — one device writes one journal per project, so two mounts would overwrite each other's ops. `bdrive web -c config.json` configures the server from a file, explicit flags winning. +`cmd/bdrive/` is a thin cobra CLI over these packages (`login`, `logout`, `init`, `stop`, `sync`, `scope`, `forget`, `status`, `log`, `share`, `export`, `import`, `url`, `hooks`, `read-log`, `web`, `whoami`, `daemon`, `version` — `mnt`/`umnt`/`remote` are gone; `init` is the front door and `stop` pauses). `export`/`import` (`migrate.go`) move a whole project between hubs with full fidelity: the archive is the remote store layout (all devices' journals + all blobs) in a tar.gz, streamed through the existing `remote.Backend` — no server-side support needed, so it works against any hub in either direction (the anti-lock-in story for cloud-hesitant users). `bdrive login` signs the device in (bare form uses the remembered server or `config.DefaultServer` = beardrive.ai; loopback-callback browser flow in `login.go`, `--device` for headless) and stores server+token+account in `settings.json`; `bdrive logout` clears the saved token+account (keeps the remembered server unless `--forget`). Switching hubs is `bdrive login ` then re-`init` — `init` is the only thing that writes a folder's remote (always a hub, `server + "/p/" + id`); there is no client command to point a folder at a raw bucket. `bdrive init` is interactive on a TTY (survey menus: create-new vs connect-existing with a project list; whole-folder vs only-some-subfolders) with full flag bypass (`--name/--project/--only/--yes`) and never prompts without a TTY; it runs the login flow first when there is no session, writes `.bdrive/config.json`, seeds `.bdriveignore`, registers agent sync hooks in each platform's USER config (`~/.claude/settings.json` and friends — once per machine, never inside a project: platforms read hook config only from the directory a session starts in, so a per-project file covers only sessions that start there and, living in a mount, would sync to the team; `Install` also migrates away hooks older versions wrote into projects), and starts sync via `startSync`; re-running it resumes — including after a folder move. **A mount is always exactly the folder named** — there is no re-rooting flag. Syncing only part of a mount is `--only wiki,docs`, which writes a bdrive-managed block of `.bdriveignore` negation rules (`cmd/bdrive/scopefile.go`; `bdrive scope add/rm` edits the same block) rather than a second scope mechanism: the old `Include` list in `config.json` is legacy — still honored, never written. Because the rules live in the synced `.bdriveignore`, scope is team-wide, which is why `sync --prune` refuses when `!` rules are present (it would strip everything outside the scope from the hub for everyone; `bdrive forget ` is the per-path tool). `init` also refuses a second folder for a project this device already syncs — one device writes one journal per project, so two mounts would overwrite each other's ops. `bdrive web -c config.json` configures the server from a file, explicit flags winning. Authentication (`webapp/auth.go`, `authlocal.go`, `mail.go`) is **mandatory in hub mode** — the config's `auth` block tunes `users_db`/`allow_signup`/`allowed_domains`/`require_verification`/`require_approval`/`admins`/`smtp`; the plain-folder viewer stays auth-free — and sits behind the `AuthProvider` interface — the OSS server ships only `BuiltinAuth` (email+password accounts and device tokens in a file-backed `auth.json`; bcrypt for passwords, SHA-256 digests for tokens, plaintext never stored; server-owned `/auth/*` pages; one-time codes for the CLI callback and device flows; SMTP reset mail with a log-link fallback). **Signup is invite-only by default** (`allow_signup` defaults false): a valid org invite bootstraps an account even when self-signup is closed — `BuiltinAuth.InviteValid` (wired to `OrgDB.ValidInvite`) lets `pageSignup`/`pageLogin` offer account creation for a `/join/` target, and `signupInvited` skips the domain/verification/approval gates and activates immediately (the invite is the vetting). `BuiltinAuth.ValidateSignupPolicy` (called at hub startup, `web.go`) refuses an ungated open hub and email-verification-without-SMTP rather than silently leaving the door open. The three postures: invite-only (default), approval-gated (`require_approval`), and domain-restricted+verified (`allowed_domains`+`require_verification`+`smtp`); `allow_signup`/`allowed_domains`/`admins` stay server-config-owned so a browser session can't widen access. A managed deployment can swap in a different provider (e.g. PropelAuth) without touching the CLI or API — keep provider-specific code out of this repo. The sync client picks up its token from `BDRIVE_TOKEN` or `settings.json` and sends `X-Bdrive-Device{,-Name,-Os}` headers (`remote/http.go`); the hub's file-backed device registry (`webapp/devices.go`) records per-device name/OS/account/server-observed IP. Journal ops carry the signed-in account (`Op.User`/`UserName` from `Session.Account`; `Author` remains the git/OS fallback). History (`webapp/history.go`): `GET /api/p//history?path=|prefix=` (newest first, device-registry join) and `GET /api/p//blob?sha=` stream any exact version — blobs are retained forever, so the future revert phase is just re-putting an old blob as a new op. Share links (`webapp/shares.go`, file-backed `shares.json`): any signed-in member mints `/s/` public URLs (`bdrive share`, or the UI's Share button) serving the file's LATEST content until revoked (optional expiry); `/s/*` responses are sandboxed (CSP `sandbox allow-scripts`, no auth cookies) so shared HTML can't attack hub sessions — keep that header on any change; `/s/*` also sits behind a per-IP token bucket (`ratelimit.go`, `share_rpm` config), and markdown share pages get a "Shared with BearDrive" footer (raw HTML is never injected into). @@ -66,13 +66,25 @@ Authentication (`webapp/auth.go`, `authlocal.go`, `mail.go`) is **mandatory in h The real coverage is the integration tests in `internal/syncer/syncer_test.go`: each test builds multiple simulated devices (`newDevice`) syncing through a shared `file://` remote (`sharedRemote`), then drives explicit `cycle()` calls to test convergence, offline operation, and concurrent-edit conflicts. Extend these when touching sync behavior — a new sync feature without a multi-device test is untested where it matters. -## Claude Code plugin +## Agent integration -`plugin/` is a Claude Code plugin (skill + `/beardrive:install` + `/beardrive:init` + `/beardrive:status` commands + turn-boundary sync hooks + a `PreToolUse` hook that auto-approves beardrive's own setup subcommands — `cmd/bdrive/approve.go`, narrow by design: a shell operator anywhere in the command disqualifies it — so onboarding is not a permission gauntlet). `/beardrive:install` (`plugin/commands/install.md`) is the team onboarding flow: binary, login, init, a consent-gated two-file agent orientation (synced `/AGENTS.md` map + repo-root `AGENTS.md`/`CLAUDE.md` pointer — see SKILL.md "Teaching agents the folder"), and user-level hooks in `~/.claude/settings.json` (blocking pull at UserPromptSubmit — which, via `bdrive sync --hook`, also injects the project's gated-link formula as additionalContext so agents append `path` [🔗](hub link) to every synced path they mention — async push on PostToolUse Write/Edit); teammates without the plugin get the same hooks from their own `bdrive init`, published via the marketplace manifest at `.claude-plugin/marketplace.json` (`/plugin marketplace add runbear-io/beardrive`). The canonical skill lives at `plugin/skills/beardrive/SKILL.md`; `.claude/skills/beardrive` is a symlink to it. The hook scripts `plugin/scripts/beardrive-{sync,pull,read,approve}.sh` (and the inline hook commands `internal/agenthooks` writes) must stay a fast no-op outside BearDrive folders — they run on every turn, and now on every machine-wide session, so the guard is pure shell and never spawns the binary outside a mount. +There is no Claude Code plugin and no bundled skill: the integration is +`internal/agenthooks` alone, and `INSTALL_FOR_AGENTS.md` (repo root) is the +onboarding runbook every agent follows. `bdrive init` registers the hooks in +each platform's USER config (`~/.claude/settings.json` and friends), once per +machine: a blocking pull at UserPromptSubmit — which, via `bdrive sync +--hook`, also injects the project's gated-link formula as additionalContext +so agents append `path` [🔗](hub link) to every synced path they mention — an +async push on PostToolUse Write/Edit, and `bdrive read-log` on +Read/Grep/Bash for the read heatmap. The inline hook commands +`internal/agenthooks` writes must stay a fast no-op outside BearDrive folders +— they run on every turn of every session on the machine, so the guard is +pure shell (a couple of `stat`s, at most one `grep` of `mounts.json`) and +never spawns the binary outside a mount. ## Docs site (`web/docs`) -`web/docs/` is the public documentation at docs.beardrive.ai — Astro 7 + [Starlight](https://starlight.astro.build) (Starlight requires Astro ^7; the cloud landing is still on Astro 5 — separate projects, so they upgrade independently), static output, Pagefind search, `llms.txt` via `starlight-llms-txt`. Unlike the hub frontend (`internal/webapp/static`) and the cloud landing page (`cloud/internal/landing/dist`) it is **not** `go:embed`ed: docs change far more often than the binary, and a search index has no business shipping in every self-hoster's install. It deploys on its own from `dist/`, so `go build` never touches it. Sidebar order is explicit in `astro.config.mjs` — a new page under `src/content/docs/` is invisible until listed there, and every page needs a `description` (meta description, search snippet, and `llms.txt` line). **The sidebar order is the recommended path, and that path is agent-first**: `Start here` (what it is → set up with your agent → your first hour) never mentions installing a binary; the CLI route lives in `Manual setup (optional)` (install the CLI, set up by hand, skills and hooks in detail) — same destination, one click away, never on the critical path. New onboarding content belongs in `Start here` and should say what to ask an agent, not what to type. `Use cases` (after `Manual setup`) holds job-shaped pages — "Share work across your team's agents", "Turn a personal brain into a company brain" — with the persona named in the first line and the `description`, not in the title; they route into the guides and never re-teach a feature. Moved URLs keep `redirects` entries in `astro.config.mjs` (static builds emit meta-refresh; real 301s live in the host config, see `web/docs/README.md`). **Guides are agent-workflow docs, not CLI tutorials** (`Working with agents`: shared agent memory, artifacts/links, read heat, scoping); command-by-command detail belongs in `reference/cli.md`. Design tokens are **generated, never copied**: `scripts/tokens.mjs` reads the `@theme` block in `internal/webapp/frontend/src/tw.css` and emits the gitignored `src/styles/tokens.gen.css`, which `src/styles/custom.css` maps onto Starlight's `--sl-color-*` — so the palette cannot drift and there is no checker to maintain (contrast the cloud landing, which keeps a copy policed by its own `check-tokens.mjs`). Because the build reads that file *outside* `web/docs`, a deploy host must check out the whole repo, not the subdirectory. Note `llms.txt` convention wants the root domain, so `beardrive.ai/llms.txt` should point at the docs subdomain — that redirect lives in the cloud landing and is the one cross-repo coordination point. +`web/docs/` is the public documentation at docs.beardrive.ai — Astro 7 + [Starlight](https://starlight.astro.build) (Starlight requires Astro ^7; the cloud landing is still on Astro 5 — separate projects, so they upgrade independently), static output, Pagefind search, `llms.txt` via `starlight-llms-txt`. Unlike the hub frontend (`internal/webapp/static`) and the cloud landing page (`cloud/internal/landing/dist`) it is **not** `go:embed`ed: docs change far more often than the binary, and a search index has no business shipping in every self-hoster's install. It deploys on its own from `dist/`, so `go build` never touches it. Sidebar order is explicit in `astro.config.mjs` — a new page under `src/content/docs/` is invisible until listed there, and every page needs a `description` (meta description, search snippet, and `llms.txt` line). **The sidebar order is the recommended path, and that path is agent-first**: `Start here` (what it is → set up with your agent → your first hour) never mentions installing a binary; the CLI route lives in `Manual setup (optional)` (install the CLI, set up by hand, hooks in detail) — same destination, one click away, never on the critical path. New onboarding content belongs in `Start here` and should say what to ask an agent, not what to type. `Use cases` (after `Manual setup`) holds job-shaped pages — "Share work across your team's agents", "Turn a personal brain into a company brain" — with the persona named in the first line and the `description`, not in the title; they route into the guides and never re-teach a feature. Moved URLs keep `redirects` entries in `astro.config.mjs` (static builds emit meta-refresh; real 301s live in the host config, see `web/docs/README.md`). **Guides are agent-workflow docs, not CLI tutorials** (`Working with agents`: shared agent memory, artifacts/links, read heat, scoping); command-by-command detail belongs in `reference/cli.md`. Design tokens are **generated, never copied**: `scripts/tokens.mjs` reads the `@theme` block in `internal/webapp/frontend/src/tw.css` and emits the gitignored `src/styles/tokens.gen.css`, which `src/styles/custom.css` maps onto Starlight's `--sl-color-*` — so the palette cannot drift and there is no checker to maintain (contrast the cloud landing, which keeps a copy policed by its own `check-tokens.mjs`). Because the build reads that file *outside* `web/docs`, a deploy host must check out the whole repo, not the subdirectory. Note `llms.txt` convention wants the root domain, so `beardrive.ai/llms.txt` should point at the docs subdomain — that redirect lives in the cloud landing and is the one cross-repo coordination point. ## PR descriptions @@ -80,10 +92,10 @@ Every PR body starts with a `## TL;DR` section: at most 5 bullet points, one sho ## Architecture diagrams in PRs -`architecture/` holds mermaid diagrams covering every application package in the repo: `overview.md` (system diagram — the package map and how the pieces connect), `cli-sync.md` (the CLI and sync engine — `cmd/bdrive` + `internal/{syncer,store,journal,config,daemon,agenthooks,agentskills}`), `webapp-server.md` (the `bdrive web` server — `internal/webapp` + `internal/remote`), and `webapp-frontend.md` (the hub's React SPA — `internal/webapp/frontend/src`). Every code change lands in exactly one detail diagram's scope; `overview.md` changes only when packages appear/disappear or the cross-piece wiring changes (`web/docs` and the private `cloud/` repo are deliberately out of scope). Before `gh pr create`: if the branch changes types or relationships drawn in any of them (new/removed types, new seams, changed fields/implements/ownership in those packages), update the affected diagram and commit it on the branch, then add an "Architecture changes" section to the PR description with, per changed diagram: (1) a sentence naming exactly which types/relationships changed and how, (2) a **Before** mermaid block and an **After** mermaid block (GitHub renders them), each an *excerpt* containing only the affected classes and their immediate relationships — never paste the full diagram. The committed diagram file stays the full current state; the before/after excerpts live only in the PR description (take Before from the diagram at the merge base). No structural change → no section, and append `# skip-diagram-check` to the `gh pr create` command to satisfy the pre-PR hook. +`architecture/` holds mermaid diagrams covering every application package in the repo: `overview.md` (system diagram — the package map and how the pieces connect), `cli-sync.md` (the CLI and sync engine — `cmd/bdrive` + `internal/{syncer,store,journal,config,daemon,agenthooks}`), `webapp-server.md` (the `bdrive web` server — `internal/webapp` + `internal/remote`), and `webapp-frontend.md` (the hub's React SPA — `internal/webapp/frontend/src`). Every code change lands in exactly one detail diagram's scope; `overview.md` changes only when packages appear/disappear or the cross-piece wiring changes (`web/docs` and the private `cloud/` repo are deliberately out of scope). Before `gh pr create`: if the branch changes types or relationships drawn in any of them (new/removed types, new seams, changed fields/implements/ownership in those packages), update the affected diagram and commit it on the branch, then add an "Architecture changes" section to the PR description with, per changed diagram: (1) a sentence naming exactly which types/relationships changed and how, (2) a **Before** mermaid block and an **After** mermaid block (GitHub renders them), each an *excerpt* containing only the affected classes and their immediate relationships — never paste the full diagram. The committed diagram file stays the full current state; the before/after excerpts live only in the PR description (take Before from the diagram at the merge base). No structural change → no section, and append `# skip-diagram-check` to the `gh pr create` command to satisfy the pre-PR hook. ## Docs to keep in sync -- `README.md` and `plugin/skills/beardrive/SKILL.md` both document CLI behavior, flags, output formats, and the on-disk layout. When changing CLI commands, flags, output, or layout, update both — the skill is what makes Claude Code beardrive-aware for end users and must match the actual binary. -- `INSTALL_FOR_AGENTS.md` (repo root) is the URL-addressable agent onboarding runbook — the canonical two-line paste prompt (README, docs, SKILL.md, `plugin/commands/install.md`, and the hub's `ConnectGuide.tsx` all point at its raw URL) tells any agent to fetch and follow it. It condenses `plugin/commands/install.md`; when that flow or the CLI steps it names change, update it too. +- `README.md` documents CLI behavior, flags, output formats, and the on-disk layout. When changing CLI commands, flags, output, or layout, update it. +- `INSTALL_FOR_AGENTS.md` (repo root) is the URL-addressable agent onboarding runbook and the source of truth for that flow — the canonical two-line paste prompt (README, docs, and the hub's `ConnectGuide.tsx` all point at its raw URL) tells any agent to fetch and follow it. When the CLI's init/login/hooks steps change, update it too. - `web/docs/src/content/docs/` is the third surface: it restates the same CLI reference, hub config, and self-hosting knobs for end users. A CLI or config change lands there too — `reference/cli.md`, `reference/hub-config.md`, `reference/project-files.md`, and the `self-hosting/` pages are the ones that go stale. diff --git a/INSTALL_FOR_AGENTS.md b/INSTALL_FOR_AGENTS.md index d281b2b..0d5bb99 100644 --- a/INSTALL_FOR_AGENTS.md +++ b/INSTALL_FOR_AGENTS.md @@ -9,8 +9,7 @@ or skip them. BearDrive syncs a folder across a team and their agents through a hub, with per-file history and share links. Setup is short by design: make sure the CLI exists, ask the user what to sync, and run **one** `bdrive init` — it signs -in, installs the skill, registers the sync hooks, starts syncing and prints -the project link. Every extra command you invent is another permission prompt +in, registers the sync hooks, starts syncing and prints the project link. Every extra command you invent is another permission prompt for the user. Full documentation: https://docs.beardrive.ai (agent-readable index at https://docs.beardrive.ai/llms.txt). @@ -24,9 +23,8 @@ the user a permission prompt, so: `! ` to run plus the allow-rule `"Bash(bdrive:*)"`, then continue with the steps that do not depend on it. -> Maintainers: this is a condensed, URL-addressable version of -> `plugin/commands/install.md` — that file is the source of truth. If the -> two disagree, update this one. +> Maintainers: this file is the source of truth for agent onboarding. When +> the CLI's init/login/hooks flow changes, update it here. ## 1. Install the CLI (only if it is missing) @@ -54,10 +52,6 @@ So: no `bdrive login`, no `bdrive login --status`. They are extra permission prompts for something init does anyway. The only time to run login on its own is when the user explicitly asks to switch hubs without connecting a folder. -Init also installs the beardrive skill for every agent platform it detects, -so later sessions understand `bdrive` conversationally. Not a separate -command either. - ## 3. Initialize the project — **ask first** Two questions: @@ -122,8 +116,8 @@ the chosen folder is git-tracked, git and BearDrive would both write it (silent-revert hazard). Get consent, then `git rm -r --cached ` and add `/` to `.gitignore`; stage but let the user commit. -Then run **one** command — init signs in if needed, installs the skill, -registers the hooks, syncs, and prints the project link. Do not precede it +Then run **one** command — init signs in if needed, registers the hooks, +syncs, and prints the project link. Do not precede it with `command -v bdrive` or `bdrive --version`: every extra command is another permission prompt, and if the binary is missing this one says so. @@ -173,29 +167,7 @@ Only if a platform the user works with is missing from init's output: run `bdrive hooks install --agent `. `bdrive hooks` shows the status table, `bdrive hooks uninstall` removes them again. -## 5. Claude Code only: install the plugin - -If you are Claude Code, also register the marketplace and plugin so future -sessions get the skill, `/beardrive:*` commands, and hooks built in: - -```sh -claude plugin marketplace add runbear-io/beardrive -claude plugin install beardrive@beardrive -``` - -If it's already installed, upgrade it instead so this machine isn't on a -stale version: - -```sh -claude plugin marketplace update beardrive -claude plugin update beardrive@beardrive -``` - -Tell the user the plugin loads on their next session (current session keeps -working via the hooks from step 4). Other platforms: init already covered -you — skip this. - -## 6. Verify, then show the payoff +## 5. Verify, then show the payoff Init printed the project's hub link and a sync summary — use them rather than running more commands. Summarize what was set up and hand the user that @@ -208,8 +180,8 @@ daemon and pending count, and `bdrive url ` links a specific file. ## Optional: teach agents about the folder — **ask first** -Offer (never do silently) the two-file orientation from -`plugin/commands/install.md` step 4: a synced `/AGENTS.md` mapping +Offer (never do silently) a two-file orientation: a synced +`/AGENTS.md` mapping the folder for the whole team (if one exists already, follow it — don't rewrite it), and a short pointer section in the repo root's `AGENTS.md` / `CLAUDE.md` so agents know the folder matters. diff --git a/README.md b/README.md index 3a7f9be..b45033d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ $ cd ~/workspace && bdrive init initialized /Users/snow/workspace server: https://your-hub project: workspace (p-7f3a2c91) - skill: installed for claude, codex claude hooks registered → /Users/snow/.claude/settings.json daemon: running (pid 55434, scan 3s, remote sync 10s) ``` @@ -144,7 +143,7 @@ hub's own storage, never something a syncing client points at directly: |---|---| | `bdrive login [server-url]` | Sign this device in (browser flow; `--device` forces the approval-link 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 ` | | `bdrive logout` | Sign this device out — clear the saved token/account (`--forget` also drops the remembered server) | -| `bdrive init [folder]` | Create/connect a project and start syncing — the mount is always exactly the folder named. Interactive on a TTY, flags (`--name/--project/--server/--only/--yes`) for scripts; installs the agent skill, registers agent sync hooks in each platform's user config (`--no-hooks` skips the hooks), prints the project link; re-run to resume | +| `bdrive init [folder]` | Create/connect a project and start syncing — the mount is always exactly the folder named. Interactive on a TTY, flags (`--name/--project/--server/--only/--yes`) for scripts; registers agent sync hooks in each platform's user config (`--no-hooks` skips the hooks), prints the project link; re-run to resume | | `bdrive stop [folder]` | Stop syncing, including agent sync hooks (files stay; `bdrive init` resumes) | | `bdrive scope [add\|rm ]` | Show or change which subfolders sync — edits the managed block of `.bdriveignore` rules that `init --only` writes, so no one hand-writes negation syntax. The daemon picks changes up in seconds; `rm` deletes nothing, locally or on the hub. `--explain` lists every path in the folder split into what syncs and what does not, so you can verify what leaves this machine (pure read — no daemon, no lock, no network) | | `bdrive forget ...` | Stop syncing a path *and* remove it from the hub — adds the rule to `.bdriveignore` (which syncs) and prunes in one step. Local files are never touched, here or on teammates' devices | @@ -152,7 +151,6 @@ hub's own storage, never something a syncing client points at directly: | `bdrive share ` | Public URL for a synced file (`--list`, `--revoke`, `--expires`) | | `bdrive sync [folder]` | Run one sync cycle now. `--note ` 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. `--prune` also removes from the hub what `.bdriveignore` now excludes (files stay on disk everywhere). `--hook