50 Commits
Author SHA1 Message Date
4ff92c56ab feat(history): group agent runs and restore any version (BEA-6) (#69)
* feat(history): group agent runs and restore any version (BEA-6)

BearDrive recorded everything and could restore nothing. Now every version
of a file has a Restore button — in the hub's History view and as
`bdrive restore` — and the changes one agent run made read as one card
instead of N loose rows.

Restore is a NEW put op pointing at the old blob: journals are never
rewritten, so one-writer-per-journal holds and peers converge on the
restore like any other edit. The hub reuses RemoteSource.Commit (the
upload commit minus the upload); the CLI writes the bytes into the working
folder and lets the ordinary cycle journal them, so the sync engine gains
no new write path.

Grouping is a pure frontend group-by on (note, device) over the existing
/history response — no journal or API change.

Known gap, stated in the UI and the docs: nothing in the hub writes a
delete op yet, so a file a run *created* cannot be un-created.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(history): don't repeat a run's note on every row in its card

UI pass on the real hub: inside a run card the note is the card's header, so
printing it again on each row said the same thing N times. The header now
carries the note (linkified, so an agent's session link still opens) and the
collapse control is its own button rather than the whole header — the link
could not live inside a button.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 17:15:46 +09:00
dcd0517e92 feat(cli): bdrive scope --explain — prove what leaves this machine (BEA-24) (#70)
The local-first claim was asserted, never demonstrated: nothing anywhere
told you what your laptop chose *not* to send. `bdrive scope --explain`
walks the folder and prints two sorted lists — synced and not synced —
with counts and a pointer at what it does not answer.

The decisions come from the same walk the sync cycle uses. scan()'s
WalkDir decision tree moves into walkFolder (internal/syncer/walk.go),
the only copy of the rules; scan and Explain both go through it, so the
output provably cannot drift from real sync behavior.

Pure read: its own Filter, no Session, no volume flock, no network.
Fully-excluded directories collapse to one counted line; nested mounts
are annotated as syncing through their own project rather than called
"not synced", which would be a lie in a trust surface.

Known gap, deliberate: this answers "what leaves from now on", not
"what is already on the hub" — the footer points at `bdrive forget`.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 17:10:51 +09:00
acb99e85e3 feat(hub): set an expiry on a share link from the UI (BEA-29) (#74)
The CLI has had --expires all along and the Public links table has always
said "no expiry" — naming an alternative the hub UI never offered. Every
link minted from the browser was permanent.

Expiry is offered AFTER minting, not before: the one-click share stays one
click, and PATCHing the token we just handed out keeps the URL already on
the clipboard valid. Minting with a TTL would instead create a second link,
since ShareDB.Create only reuses permanent ones.

- ShareDB.SetExpiry re-dates a live share in place; repo-write failure
  restores the previous row rather than deleting it (unlike Create's
  rollback, this row already existed — dropping it would revoke a live
  link over a disk hiccup).
- PATCH /api/shares/{token} mirrors handleShareRevoke: resolve the token,
  requirePerm(PermWrite), act. Duration parsing is copied from
  handleShareCreate so the two routes can't drift.
- Share dialog gets an Expires select (Never / 24h / 7d / 30d). A failed
  PATCH toasts and reverts the control. Copy link keeps the dialog's
  initial focus — the new control would otherwise have taken it.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 17:05:06 +09:00
4e34d03e14 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>
2026-07-29 10:08:45 +09:00
Snow LeeandClaude Fable 5 22461a3b4f chore(release): v0.11.0 — changelog, plugin 0.4.0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016aYntCWwdUhpzUfEk3ddyJ
2026-07-27 16:09:16 -07:00
9088127176 feat(sync): bdrive forget + sync --prune to take ignored paths off the hub (BEA-20) (#68)
Adding a path to .bdriveignore only stopped future uploads: anything that
synced before the rule existed stayed on the hub forever, with no command
that removed it without deleting it from local disk on every device.

Two engine changes make an explicit removal safe:

- materialize's delete loop now consults the filter. A cached path absent
  from the replayed target that the rules exclude is dropped from tracking
  instead of unlinked — without this, any delete op for a now-filtered path
  wipes every peer's local copy, which is the data loss this issue is about.
- the filter is reloaded mid-cycle from the pulled .bdriveignore, before
  materialize. A peer receiving the new rules and the deletes they justify
  in one batch would otherwise materialize with stale rules and the guard
  would never fire. materialize's write side is split into materializeFile
  so the ignore file can land on its own.

On top of that, Session.Prune journals a delete for every path the replayed
state still holds that the SHARED rules exclude — reconciling against the
replay, not the local cache, because a path filtered out in an earlier cycle
was dropped from the cache back then and is invisible locally today. The
rules are deliberately ignore-only: the include scope lives in each device's
own .bdrive/config.json and does not sync, so pruning against it would let a
narrow-scope device delete a whole-folder teammate's files.

Plain `bdrive sync` and the daemon are unchanged — pruning is never a side
effect of editing .bdriveignore.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 07:22:11 +09:00
eba6fe1a64 fix(cli): stop pointing users at a device list that doesn't exist (BEA-13) (#63)
`bdrive logout` and the beardrive skill both told users the device token
"stays valid until it expires — revoke it from the hub's device list".
All three parts are false: there is no device list page or route, device
tokens carry no expiry field, and logout makes no server call at all — it
only rewrites the local settings file.

Both strings now say what is true. The CLI note moves to a package-level
`logoutNote` const so `login_test.go` can assert it, plus SKILL.md's logout
row, mentions neither a device list nor expiry.

Correcting the strings only; the real device list + revoke route touches
the `AuthProvider` seam and is filed separately.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 07:05:19 +09:00
589be3a6de fix(hub): open the project Dashboard to every member, rename /insights → /dashboard (BEA-12) (#62)
* fix(hub): open the project Dashboard to every member, rename /insights → /dashboard (BEA-12)

"Dashboard" was the first sidebar item a new member clicked and it always
refused: it landed on /<project>/insights showing "Insights is for hub
admins and org owners." The gate was client-side only — GET /heat is
gated on project membership and returns counts without actor identities,
so every member's browser could already fetch every number the page draws.

Drops the canInsights gate (nav item, dedicated route, project-home
embed, ⋯ menu entry) and renames the view route insights → dashboard so
the nav label, the URL and the page title finally agree. The shipped
/insights URL still resolves and normalizes to /dashboard (LEGACY_VIEWS
in router.ts) so bookmarks don't 404 and only one URL stays live.

No server change: /heat gating and response shape are untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(architecture): router VIEW_ROUTES now names dashboard, with LEGACY_VIEWS

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 07:05:00 +09:00
Snow W. Lee (Sungwon)andGitHub 65d4df5a84 One paste-able URL onboards any agent: INSTALL_FOR_AGENTS.md (#57) 2026-07-27 20:23:42 +09:00
056c883204 fix(sync): anchor --shared include entries to the mount root (BEA-5) (#56)
`bdrive init --shared wiki` wrote `include: ["wiki/"]`, which compile()
treats as an unanchored gitignore pattern — so any nested directory named
`wiki` synced too. Shared-subfolder mode is what people use to keep private
material out of a project, and it was silently widening the scope: 15 files
under .agents/, .claude/ and .gemini/ leaked into a real project from
.../detector/shared/ dirs.

cleanShared now emits "/wiki/", which fixes both callers (init --shared and
bdrive scope add). config.LoadProject anchors legacy single-segment entries
on read, so the existing mounts are fixed without a re-init — and that also
keeps `bdrive scope rm wiki` working against pre-fix configs, with a
belt-and-braces unanchored candidate key in scopeRemove for any config that
bypasses LoadProject.

Not touched: compile() itself, and no delete op for the already-leaked
remote files (BEA-20 — a delete would unlink teammates' local copies).

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 18:44:05 +09:00
2c85747464 feat(sync): .bdriveignore always syncs, regardless of scope or rules (#54)
The ignore file is exempt from filtering in Filter.Skip: it syncs even on
--shared mounts (where it sat outside the include list and was local-only)
and even when one of its own patterns matches it. One guard covers both
scan and materialize since they share the filter. Docs updated on all
three surfaces (SKILL.md, install.md, web/docs).


Claude-Session: https://claude.ai/code/session_01G3AdFdps7seYbW6FhyK58t

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:31:53 +09:00
0236e1b272 feat(cli): multi-folder --shared at init + bdrive scope to edit the sync scope (#53)
* feat(cli): init --shared accepts multiple subfolders (repeatable or comma-separated)

--shared is now a slice flag: `--shared wiki --shared docs` or
`--shared wiki,docs` sync several subfolders into one project
(include list ["wiki/", "docs/"]). The interactive scope prompt takes a
space- or comma-separated list. Entries resolving to ".", "", or ".."
error out — silently dropping them would widen scope to the whole folder.

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

* docs(plugin): skills/commands propose multiple --shared folders at init

The init/install flows now scan for all knowledge folder candidates and
offer them as one --shared list (one project, one permission set), noting
that folders needing different access belong in separate projects.

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

* feat(cli): bdrive scope — add/remove shared subfolders without editing JSON

`bdrive scope` shows the include list; `scope add`/`scope rm` edit it from
the mount root. The daemon re-reads config each tick, so changes apply in
seconds. rm deletes nothing (newly filtered paths drop from the cache with
no delete op); removing the last entry is refused since an empty include
list means whole-folder sync. add onto a whole-folder project is refused
for the same narrowing hazard. Skill/README/docs updated; scope added to
the cli-sync diagram's command list.

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

* docs+cli: scoping guide covers multi-folder --shared and bdrive scope; init hints on ignored --shared at resume

The scoping guide (the dedicated page for this feature) now shows
--shared wiki,docs and a "Change the scope later" section for bdrive
scope; setup-by-hand and project-files point at it. init resume with an
explicit --shared now says the flag is ignored instead of staying silent.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 14:49:58 +09:00
69e7231a70 feat(hub): per-project permissions — none/read/write/admin, invite-only projects, honest degraded sync (#46)
Access was binary and org-wide: any org member got full read+write on every
project. Now each project carries four ordered levels, resolved by one
resolver and enforced at one choke point.

- `projectPerm` (perms.go) replaces `projectAllowed`; `proj(level, h)` in
  server.go gates every per-project route by the level it declares at
  registration, so no handler grows its own check.
- `Project` gains Creator/Default/Perms. `Default == ""` means write, so an
  upgraded hub behaves identically until someone edits permissions.
- Creator becomes the first project admin; org owners are implicitly admin
  everywhere in their org and a grant naming one is refused, not ignored; a
  project always keeps at least one explicit admin.
- Default `none` makes a project invite-only. A `none` member is treated
  exactly like a non-member, including on create-or-join by name.
- Rename/delete move from org-owner-only to project `admin`.
- Both metadata backends persist it: the file store rides along, the SQL
  store gains `project_perms` plus an idempotent ALTER for the two new
  columns (migrate() had only ever created tables).

Client side, a refusal stops looking like an outage: `remote.ErrForbidden`
plus `Result.ReadOnly` (push refused → pull-only) and `Result.NoAccess`
(pull refused → paused, working folder untouched). Neither sets Offline,
neither loses a local op, and re-granting self-heals on the next cycle.
`bdrive status`/`sync` and the daemon (once, on transition) say which.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 10:41:57 +09:00
6628718f1d fix(cli): agent hooks never sync folders this device didn't opt into (#44)
* fix(cli): agent hooks never sync folders this device didn't opt into

The turn hooks decided "this folder is managed" from the mere presence of
.bdrive/config.json — a file designed to travel with the folder. Two holes:

- A config.json arriving via git clone / copied dir made one hook firing
  silently mint a device identity, register the mount, create a volume
  store, journal the whole folder, and inject the hub-link formula — on a
  device that never ran init or login.
- `bdrive stop` only killed the daemon: the next agent turn's
  `bdrive sync --hook` resumed a full sync cycle and kept injecting links,
  and `stop --forget` was undone within one turn by registry self-heal.

Fix: one gate (`syncBlocked`) in the paths all hooks route through —
sync/sync --hook/read-log now require the mount to already be enrolled in
this device's mounts.json (read without ResolveMount's enrolling
self-heal) and not paused. Hook mode exits silently; plain `bdrive sync`
errors with a `bdrive init` pointer. New per-device paused marker in the
volume dir: set by `bdrive stop`, cleared by `bdrive init` (startSync).
Only init enrolls or resumes; folder moves still self-heal since
enrollment is keyed by mount id, not path.

Docs updated (README, SKILL.md, docs cli reference, CHANGELOG). Tests:
hook/read-log no-op + no-enrollment on unenrolled and paused mounts,
plain-sync refusals, stop→pause→forget regression, paused marker contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: architecture-diagram PRs must show before/after excerpts of changed classes

The "Architecture changes" PR section now names exactly what changed and
shows Before and After mermaid excerpts scoped to the affected classes and
their immediate relationships — never the full diagram (Before = merge
base). Convention updated in CLAUDE.md, architecture/README.md, and the
pre-PR hook's reminder text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: add cli-sync architecture diagram; widen diagram convention to the CLI

architecture/cli-sync.md draws the CLI and sync engine (cmd/bdrive +
internal/{syncer,store,journal,config,daemon,agenthooks}): the Session
cycle over Store/journal/remote, and the command layer with the new
syncBlocked opt-in gate, paused marker, and enrollment ownership. The
pre-PR hook and CLAUDE.md now watch these packages too, so CLI-side
structural changes trigger the before/after-excerpt convention the same
way server changes do.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: full-coverage architecture diagrams — overview, frontend, agentskills

Every application package is now drawn somewhere: overview.md (system
diagram — package map, device↔hub↔storage flow, agent surfaces, and the
private cloud/ repo as an external seam consumer), webapp-frontend.md (the
hub SPA's modules: App/HubApp/VolumeApp/Browser, the in-repo nav/router,
api layer, hooks, components), and agentskills added to cli-sync.md. The
pre-PR hook now watches all of cmd/, internal Go code, and frontend/src
(generated static/ excluded); CLAUDE.md and architecture/README.md state
the coverage rule: every code change lands in exactly one detail diagram's
scope, web/docs and cloud/ deliberately excluded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: PR bodies start with a TL;DR — max 5 informal one-liners

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:53:23 +09:00
4f5f517a6f feat(cli): bdrive export/import — move projects between hubs with full history (#43)
The anti-lock-in story for cloud-hesitant users: export a project's
complete store (every device's journal + every blob, i.e. full history
and authorship) into a portable tar.gz, and import it as a fresh project
on any other hub — cloud → self-hosted or back.

The archive is simply the remote store layout plus a manifest, streamed
through the existing remote.Backend, so no server-side support is needed
and it works against every existing hub. Import verifies each blob's
content hash, rejects foreign tar entries, requires an empty target
project, and refuses journal-less archives. Reconnecting devices resume
exactly where they were, because their journals are byte-identical.

Docs: README + SKILL.md command tables, docs-site CLI reference section,
and a new step-by-step reference page (Migrate between hubs).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 19:04:05 -07:00
34afef17cc fix(cli): onboarding friction — headless login fallback, --version, init next steps, labeled authorship (#39)
BEA-7 launch-critical set from the onboarding audit:

- login: shells without a TTY auto-fall back to the device-code flow
  (agents/CI/SSH no longer hang on the browser callback); a failed
  browser open also falls back, and the waiting state hints --device
- bdrive --version now works (cobra root Version), same output as
  bdrive version
- init prints a next-steps block: daemon auto-sync note, the
  'bdrive init --project p-xxx' connect command for teammates,
  bdrive log / bdrive share
- authorship: the daemon re-reads settings.json every tick so a
  login/logout/account switch is reflected in op authorship instead
  of stamping a stale identity forever; whoami now shows the
  signed-in account and labels the git/OS author as the signed-out
  fallback

Docs updated in README, plugin SKILL.md, and web/docs reference/cli.md.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 09:35:48 -07:00
Snow LeeandClaude Opus 4.8 5c6099177b feat: bdrive skill install + one-paste Codex/Hermes setup guide
The hub's install guide told Codex and Hermes users to run four CLI
commands by hand, and the one people skipped — `bdrive hooks install` —
is exactly the one that makes files sync at turn boundaries. Hand the
setup to the agent instead, the way the Claude tab hands it to the
plugin.

- `bdrive skill install` (internal/agentskills, plugin/embed.go): the
  binary now carries the beardrive skill and writes it to any agent that
  reads SKILL.md — ~/.{claude,codex,gemini,hermes}/skills/beardrive/.
  User-level on purpose: the skill is about the CLI, not one folder, and
  a synced project folder should never carry it. Idempotent; refreshes a
  stale copy after a CLI upgrade. Bare `bdrive skill` prints the table,
  mirroring `bdrive hooks`.
- Guide's Codex/Hermes tabs are now a single paste, no terminal: the
  prompt has the agent install the CLI, keep the skill, sign in, init,
  and register hooks. The commands ride inside the prompt because these
  agents ship no BearDrive knowledge (Claude's tab is terse only because
  the plugin carries it). `login --device` there — a browser-callback
  sign-in is invisible to an agent mid-turn, while the device flow gives
  it a code and URL to relay. Plain commands live on in an "or run it
  yourself" fallback.
- Docs realigned: README, SKILL.md, /beardrive:install, self-hosting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 14:30:06 -07:00
Snow LeeandClaude Fable 5 a6ee5aa75e docs+skill: fold live-e2e findings into onboarding docs; add onboarding-e2e skill
From the first agent-conversation e2e against a live hub:
- install.md: git handoff for tracked knowledge folders (one transport per
  folder), .bdrive//.bdriveignore git guidance, Hermes per-user hook warning,
  drop stale share--list aside
- SKILL.md: .bdriveignore is local-only on --shared mounts (root file sits
  outside the include list) — doc now matches behavior
- new project skill .claude/skills/onboarding-e2e: reusable procedure to
  re-run the conversation-level e2e (staging, instruction materialization,
  agent prompt contract, verification, pass bar)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbiaaVM2ACxeRi8ySG9ybc
2026-07-18 21:25:35 -07:00
Snow LeeandClaude Fable 5 66f7a98fcc feat(plugin): agent-first onboarding — cloud-default login, wiki auto-detection, hub-URL payoff
- login copy across README/skill/commands: bare `bdrive login` (BearDrive
  Cloud) is the default; signup auto-creates a free personal workspace,
  pending invites route into the team; self-hosted hubs pass a URL
- init flows propose a detected knowledge folder (wiki/docs/notes/handbook/
  vault) as --shared instead of asking open-endedly; repo-root rule restated
- install always ends by handing the user a `bdrive url` link to a
  representative page — the activation moment

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbiaaVM2ACxeRi8ySG9ybc
2026-07-18 16:25:13 -07:00
Snow LeeandClaude Fable 5 3cb39ebf35 docs: --hook flag in README/SKILL command tables + CLAUDE.md plugin note
The docs-sync rule: CLI flag changes update both README and SKILL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-16 10:28:25 -07:00
Snow LeeandClaude Fable 5 644caff70e feat(hooks): every mentioned file path gets a gated hub link — formula injected each turn
Field report follow-up: an agent with a stale skill copy couldn't find
the gated URL after creating a file. Instructions rot; hook output is
computed fresh from the binary every turn. Claude Code's turn-start
pull hook now runs 'bdrive sync --hook claude-code', which:

- pulls as before, and stamps the session note from the event JSON
  (replacing the sh/sed pipeline for the pull leg)
- emits the project's gated-link formula as UserPromptSubmit
  additionalContext: whenever the agent mentions a synced file path in
  prose, it appends the hub link on an emoji — `<path>` [🔗](<url>) —
  path plain (it's the local path), hyperlink on the emoji only; code
  blocks stay plain; bdrive share stays explicit-opt-in-public

Blind-tested: an agent given only the injected context decorated every
path mention correctly, kept the code-block command plain, and checked
files were synced before linking.

- hooks install now CONVERGES marker-identified groups to the current
  shape (command/matcher/flags), so improvements reach existing
  projects on reinstall instead of being frozen by the idempotency
  marker; hermes same
- plugin: UserPromptSubmit → beardrive-pull.sh (stdout passes through);
  version 0.3.0
- SKILL 'Share what you make' generalized to 'Link what you mention'
  (URL formula documented for non-Claude platforms); install.md pointer
  template updated

Never fails the turn: every error path in --hook mode is a silent
successful exit; offline still emits (links serve online teammates).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-16 10:25:07 -07:00
Snow LeeandClaude Fable 5 f93aba401b chore(plugin): bump plugin version to 0.2.0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 16:33:01 -07:00
Snow LeeandClaude Fable 5 9c8c782330 feat(gtm): round 6 — confirmation pass, goal complete; post-exit polish
Rounds 5 and 6 both scored 9/8/9/9/8/9 with zero repo-fixable
blocker/major findings — the exit bar (two consecutive passes) is met.
Round 6 truth-verified the loop's copy against the code (commands,
Insights sections, hook platforms, url format — all accurate; zero
fabricated proof). Post-exit optional minors applied: og:image +
twitter card meta on the site, SKILL login-cell waitlist parity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-15 12:27:54 -07:00
Snow LeeandClaude Fable 5 1bd69cc506 feat(gtm): round 5 — first pass (9/8/9/9/8/9); apply its two optional one-liners
Zero repo-fixable blockers/majors; all four round-4 surfaces verified
fixed. The two optional minors applied: the README command-table login
cell and SKILL's init cell now carry the waitlist-only caveat inline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-15 12:23:29 -07:00
Snow LeeandClaude Fable 5 26e67e3188 feat(gtm): round 4 — every try-path is self-host-first, no surface defaults into the dead cloud
Reviewer round 4 (9/6/9/9/7/9) caught that round 3 only fixed the
Quick start: the bare-login default survived in the four highest-
traffic surfaces. All inverted to self-host-first:

- README HERO code block (+ the other-machine one-liner and the
  every-other-device shorthand): bdrive login https://your-hub, with
  Cloud as a clearly-labeled waitlist callout
- plugin/commands/install.md + init.md step 2: ask for the team's hub
  URL; bare 'bdrive login' explicitly flagged as targeting the
  not-yet-open Cloud
- SKILL.md walkthrough step 1 + the hub-example comment
- website hero terminal animation: 'syncing → your team's hub', share
  URL on a neutral acme-hub.example host (og:url and the coming-soon
  card untouched — those are honest)
- README's login reference sentence now says the beardrive.ai default
  is waitlist-only

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-15 12:19:39 -07:00
Snow LeeandClaude Fable 5 00eee2c5ed feat(gtm): round 1 — truth pass, positioning unification, community on-ramp, demo assets
GTM reviewer round 1 (baseline: positioning 6, funnel 4, open-paid 8,
community 3, launch 3, metrics 2; 3 blockers + 3 majors, all repo-fixable):

- TRUTH: removed the false 'no server required' claim (x3 on the
  website) and stale 'S3/GCS directly' sync claims in both plugin
  manifests — hub-only since v0.3.0
- POSITIONING: one line everywhere ('the open-source Google Drive for
  AI agents' + the attribution/read-analytics wedge) across README,
  website, marketplace.json, plugin.json, SKILL.md; third 'Why' card
  and AGPL-why rationale; nav CTA → Star on GitHub
- COMMUNITY: CONTRIBUTING.md, issue forms + PR template, public dated
  ROADMAP.md, CHANGELOG.md seeded from all 8 releases
- DEMO ASSETS: real product screenshots captured from the seeded demo
  hub (Insights treemap, browse-with-heat, public share page) embedded
  in README + website — no fabricated content anywhere
- DOCS: self-hosting quickstart, metrics plan (north star: active
  shared brains), launch-plan drafts (Show HN/PH/launch week), and
  docs/gtm-handoff.md — the prioritized external-action checklist

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-15 11:56:14 -07:00
Snow LeeandClaude Fable 5 5ff82c2c49 feat(cli): bdrive url — internal, permission-walled links agents share when they create files
New command printing a file's hub viewer URL: sign-in + project-org
membership required to open (the internal counterpart to bdrive share's
public links). Computed locally from the mount config — hub origin +
project id from the remote, path segments percent-encoded with literal
slashes; unsynced paths (ignored, or outside a --shared scope) are
refused so nobody gets handed a 404. --sync pushes first so a
just-created file's link resolves immediately; no arg = project home.

The plugin docs now instruct agents to include this link in their reply
whenever they create a shareable artifact (.md/.html/.csv/...) in the
shared folder, reserving bdrive share for people outside the hub:
SKILL.md command map + 'Share what you make' guidance, install.md root
pointer template + payoff step, README, CLAUDE.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-14 11:29:54 -07:00
Snow LeeandClaude Fable 5 476113ebdc fix(hooks): read heat captures grep matches and shell-command reads, not just Read
Field report: an agent session read a dozen-plus files via Bash
(grep/cat/tail/find) and Grep, but read heat showed almost nothing —
the read-log hook only matched the native Read tool.

- matchers broadened per platform: claude Read|Grep|Bash, codex
  read_file|shell, gemini +search_file_content|run_shell_command,
  hermes read_file|grep|bash; plugin hooks.json matches Read|Grep|Bash
- read-log is now tool-aware: shell events mine the command line for
  existing files it names (redirect targets and flags excluded), grep
  events mine the response for the files the matches came from
  (content lines and filename lists), and listing tools (Glob, ls) are
  deliberately ignored — seeing a file's name is not reading it
- hooks install upgrades a registered hook's stale matcher in place, so
  re-running it after a binary upgrade rolls coverage out to existing
  projects instead of being skipped by the idempotency marker
- docs: SKILL.md platform table + read-heat wording, install/init
  commands, README command table

Note from the same report, verified not a bug: read-log resolves the
mount via the folder's own .bdrive/config.json (config.ResolveMount),
so a stale duplicate registry entry cannot swallow reads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-13 15:05:05 -07:00
Snow LeeandClaude Fable 5 8cd886174b docs(plugin): two-file AGENTS.md orientation for shared folders
Replace the old "append conventions to CLAUDE.md" step with a two-file
pattern for teaching agents a newly mounted shared folder:

- <shared>/AGENTS.md (synced): the team's single source of truth for the
  folder's structure and conventions — scaffolded once by the project
  creator, read (never rewritten) by joiners.
- A repo-root AGENTS.md/CLAUDE.md pointer (per machine, not synced): the
  awareness/routing layer. Required because discovery differs by
  platform — Claude Code and Hermes load nested instruction files lazily
  (only after entering the folder), and Codex never discovers them at
  all (root→cwd path only).

SKILL.md gains a "Teaching agents the folder" section with the platform
discovery table and a first-contact orientation ritual; /beardrive:install
step 4 and /beardrive:init step 6 now offer both files as separate
consents; install.md step 3 gains the same never-sync-a-repo-root hard
rule init.md already had; README/CLAUDE.md descriptions updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-12 18:30:07 -07:00
Snow LeeandClaude Fable 5 c982c54191 docs(plugin): gbrain coexistence — one enrichment owner per shared folder
Pre-existing working-tree edits landed with this batch: SKILL.md and
/beardrive:init gain guidance for gbrain-backed shared folders — elect a
single enrichment owner (everyone else indexes read-only), route member
captures into per-member subtrees, treat conflict copies as the canary
for a second enricher, and follow the owner's pull→import→enrich→write
cycle discipline to avoid silently reverting human edits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-12 16:54:28 -07:00
Snow LeeandClaude Fable 5 24d40fb4b5 docs: describe the layered Insights dashboard and heat by=device
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-12 07:36:37 -07:00
Snow LeeandClaude Fable 5 e3ca821dc4 feat(web): Insights quadrant + read-heat docs (phase 2)
Admin/org-owner Insights view (⋯ menu): dependency-free SVG scatter of
every file by 30-day reads × days since last change, log scales, with the
hot-but-stale danger quadrant shaded and a ranked fix-these-first list;
lens toggle for all/human/agent reads. The Claude Code plugin gains a
PostToolUse(Read) hook so plugin users feed agent-read telemetry without
project-level hook registration. Docs synced: README, SKILL.md,
plugin install/init commands, CLAUDE.md, design doc marked implemented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-11 14:54:32 -07:00
Snow LeeandClaude Fable 5 626a9c0a07 feat(cli): bdrive hooks — agent-agnostic sync hook registration
`bdrive hooks install` detects the agent platforms in use — Claude Code
(.claude/), Codex (.codex/), Gemini CLI (.gemini/), Hermes (~/.hermes/) —
and idempotently merges beardrive's turn-boundary sync hooks into each
platform's own hook config (JSON for claude/codex/gemini, YAML for
hermes), preserving existing hooks. All four pipe hook JSON with a
session_id, so one POSIX-sh hook command serves every platform: pull at
turn start, push after edits, changes stamped "<agent> session <id>".
Bare `bdrive hooks` prints the detection/registration table.

The beardrive skill now runs it automatically after `bdrive init`, and
/beardrive:install's hand-maintained settings.json block is replaced by
the command, so the hook content has one source of truth in the binary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-11 10:57:07 -07:00
Snow LeeandClaude Fable 5 c1f0d0f8ee feat(sync): session-linked notes — stamp changes with the agent session
`bdrive sync --note <text>` stamps session context onto every op the cycle
commits and persists it in the volume store (note.json, --note-ttl default
30m) so the daemon's own scans stamp it too — winning the race between
one-shot hook syncs and the 3s daemon scan. The plugin sync hook extracts
session_id from hook stdin JSON and passes it automatically, so history
links every change to the Claude Code session that made it. Conflict-copy
ops keep their own note; expired/cleared notes stop applying.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-11 10:56:53 -07:00
Snow LeeandClaude Fable 5 10a9998672 feat(sync): fence nested mounts; knowledge-init flows in plugin docs
A subdirectory with its own .bdrive/config.json now syncs only through
its own project: the parent mount never scans into it, materializes
over it, or emits deletes for paths under it (cached paths are dropped
without delete ops, the same posture as newly ignored files). Detection
is presence-based (config.IsMount) so even a corrupt nested config
still fences. This makes the team-knowledge-folder-inside-a-personal-
mount topology safe.

The plugin's init command and skill gain a "connecting knowledge
tooling" playbook: a detection ladder (gbrain -> OKF wiki -> plain
docs folder -> starting-point menu) and two hard rules — one transport
per folder (git-tracked dirs are handed off via git rm --cached +
gitignore before syncing) and knowledge-as-scoped-subfolder (never a
repo root). Conflict copies documented with their exact glob
(*.bdrive-conflict-*), since openknowledge validate cannot see them.

New flow tests: shared-subfolder scope both ways, teammate connect
over identical/divergent local content, nested mount syncing
independently on two projects, and a guard pinning the conflict-copy
filename to the documented glob.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-10 21:42:36 -07:00
Snow LeeandClaude Opus 4.8 b06d0d6a32 feat(cli)!: hub-only — remove remote command; reposition docs
BearDrive is now hub-based only. Clients sync through a `bdrive web` hub
over https and never talk to object storage directly.

- Remove the `bdrive remote` / `bdrive remote set` command entirely. Wiring a
  folder's remote is `bdrive init` (always a hub); switching hubs is
  `bdrive login <url>` then re-`init`. Object storage stays as the *hub's*
  backing store (`bdrive web s3://…`), never a client remote.
- Reposition README, CLAUDE.md, and the beardrive skill from
  "no server required / direct-to-bucket" to hub-based; section 2 of the
  skill is now "storing a hub's data" (server-side), and the stale
  `init --remote` / `remote set` examples are fixed.

BREAKING: direct client-to-bucket sync is no longer supported.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-10 09:55:58 -07:00
Snow LeeandClaude Opus 4.8 074cc4bfa8 feat(cli): add bdrive logout; clarify remote set host kinds
- New `bdrive logout` clears the saved token + account (folders untouched);
  `--forget` also drops the remembered server. Switching hubs is
  `bdrive login <new-url>` then re-`init`.
- `remote set --help` now explains the two remote kinds — object storage
  (s3/gs/file) vs a bdrive hub (https://<server>) — with examples, instead of
  leading with only s3://. (The https:// hub scheme was already accepted.)
- Docs updated (README, CLAUDE.md, SKILL.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-10 09:01:11 -07:00
Snow LeeandClaude Opus 4.8 62175e29a9 feat(web): database config (file/sqlite/postgres) + docs (phase 3)
Wire a `database` block in bdrive web config: {driver: file|sqlite|postgres,
dsn}. Default file, unchanged (existing per-registry paths honored). sqlite
or postgres routes all five hub registries through the SQL MetaStore.

Verified: signup + project + invite round-trip survives a restart on BOTH
file and sqlite; the sqlite DB holds real relational tables/rows (not JSON
blobs). Docs: a "choosing a database" section (README), the persistence
architecture note (CLAUDE.md), and a config mention (SKILL.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-10 06:54:49 -07:00
Snow LeeandClaude Opus 4.8 bd7ea16200 feat(web): invite-only signup by default; harden signup postures
A hub on a public URL could be signed up to with any fake email. Make the
safe posture the default and turn the dangerous ones into startup errors.

Three supported postures:
- invite-only (NEW default): allow_signup defaults false. A valid org
  invite link bootstraps an account even when self-signup is closed — the
  only way in. pageLogin/pageSignup detect a /join/<token> target via
  BuiltinAuth.InviteValid (wired to OrgDB.ValidInvite) and offer account
  creation; signupInvited skips the domain/approval/verification gates and
  activates immediately (the owner's invite is the vetting).
- approval-gated: allow_signup + require_approval (no SMTP needed).
- domain-restricted + verified: allow_signup + allowed_domains +
  require_verification (needs smtp).

Hardening:
- BuiltinAuth.ValidateSignupPolicy (run at hub startup in web.go) refuses to
  boot an open hub with no gate, or require_verification without a mailer
  (the link would otherwise only reach the server log).
- handleAdminPolicy rejects enabling verification without SMTP; the UI
  toggle is disabled with clearer copy in that case.

Tests: TestValidateSignupPolicy, TestInviteBootstrapsAccountWhenSignupClosed,
TestPolicyVerificationNeedsMailer. Verified end to end in the running hub
(outside-domain invitee onboards via a link; direct signup stays closed).
Docs updated (README, CLAUDE.md, SKILL.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-09 16:57:49 -07:00
Snow LeeandClaude Opus 4.8 0254c8d464 feat(web): native URL path routing (no hash, no %2F)
Replace the frontend's hash-based routing with the History API and real
`/` paths, so URLs read like native file paths:

  /#p-4e61c7d4/shared%2Fidea.md  ->  /p-4e61c7d4/shared/idea.md
  /#shared%2Fnotes.md            ->  /shared/notes.md          (volume mode)
  /#join/<token>                 ->  /join/<token>

Client (app.js):
- parseRoute() reads location.pathname; pushURL/syncURL push native paths
  (segments percent-encoded, "/" kept literal); a popstate handler restores
  back/forward. Invites read from /join/<token>.
- All api/ fetches and the app.js/style.css refs are now root-absolute so a
  deep path doesn't break relative URL resolution.

Server (server.go):
- New Server.frontend handler: real assets serve directly; every other
  non-API/auth/share GET returns index.html (SPA fallback), so deep links
  and refreshes resolve instead of 404ing. Reserved prefixes stay 404s.
- Invite links minted as /join/<token> (orgs.go).

Tests: TestFrontendSPAFallback covers the fallback + reserved-prefix 404s;
existing invite test updated. Verified end to end in the running hub
(deep-link reload, back/forward, no %2F/# in the bar). Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-09 14:19:07 -07:00
Snow LeeandClaude Fable 5 4137fca0ef docs: organizations, quota seam, share footer/rate limit in README, SKILL, CLAUDE.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 17:10:12 -07:00
Snow LeeandClaude Fable 5 34b2ca184c relicense: MIT -> AGPL-3.0
LICENSE is the verbatim GNU AGPL-3.0 text; the Runbear, Inc. copyright
notice lives in the README license section (kept verbatim so GitHub's
license detection works). README also now documents the OSS/managed
boundary: everything here is self-hostable including teams; the managed
service adds only hosting, SSO, billing/quotas, backups, support.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 16:52:31 -07:00
Snow LeeandClaude Fable 5 f12710d527 docs(plugin): retire the "mount" framing; /beardrive:mount -> /beardrive:init
bdrive mnt was replaced by bdrive init, but the plugin still taught the
old model — /beardrive:mount even instructed Claude to run a command
that no longer exists.

- /beardrive:mount -> /beardrive:init: login-aware, uses bdrive init
  (resume/create/connect, --shared), points at /beardrive:install for
  the full team setup.
- Skill description reframed around "start syncing a project"; "mount a
  folder" kept as a trigger phrase since people still say it.
- status command, marketplace manifest, README plugin section, and
  CLAUDE.md updated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 15:09:11 -07:00
Snow LeeandClaude Fable 5 c0e785a4ba feat: public share links and /beardrive:install for Claude Code teams
Share links (bdrive share <file>, or the web UI's Share button):
- Mint an unguessable public URL (/s/<token>) anyone can open — no
  account. HTML renders as a page, markdown gets a standalone shell,
  PDFs open inline; ?download=1 attaches.
- Sandboxed: /s/* responses carry CSP `sandbox allow-scripts` + nosniff
  and never see auth cookies, so shared content's scripts run in an
  opaque origin and can't touch hub sessions.
- Links serve the file's LATEST synced content and live until revoked;
  --expires makes self-destructing ones; --list/--revoke manage them.
  Re-sharing a file returns the same link. File-backed shares.json.
- CLI resolves the project by walking up to .bdrive/ from the shared
  file, warns when the hub address is private (LAN-only links), and
  hints when the file hasn't synced yet.

/beardrive:install (plugin command) — team onboarding driven by Claude:
- Ensures the bdrive binary, signs in (bdrive login), runs bdrive init
  (whole folder or a shared subfolder like wiki/).
- Asks before appending a CLAUDE.md section that teaches agents to put
  shareable artifacts in the shared folder and mint URLs with bdrive
  share; asks before registering project-level hooks in
  .claude/settings.json: blocking pull at UserPromptSubmit, async push
  on PostToolUse Write|Edit — teammates sync with or without the plugin.
- Fix: the plugin hook script still checked for the old `.bdrive` file
  and was a silent no-op since the directory change; now checks -d.

Tests: share creation gating, public access + sandbox headers, dedupe,
latest-content semantics, revoke, expiry, markdown/download variants,
list filtering, registry persistence. Docs updated (README sharing +
Claude Code sections, SKILL.md, CLAUDE.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 15:02:13 -07:00
Snow LeeandClaude Fable 5 2d7f2f8bfa feat: auth, move-proof projects, interactive init/login, web history
Authentication (previous phase, now landed together with its follow-ups):
- Email+password+name accounts behind an AuthProvider interface; the OSS
  server ships BuiltinAuth only (file-backed auth.json: bcrypt password
  hashes + SHA-256 token digests, plaintext never stored; server-owned
  /auth/* pages; managed deployments can swap in another provider).
- bdrive login: loopback-callback browser flow (sign-up on the page, the
  terminal finishes itself) with a device-code fallback for headless
  machines; long-lived revocable device tokens in settings.json.
- Password reset via plain SMTP (stdlib) with a log-link fallback when no
  SMTP is configured.

Move-proof projects:
- .bdrive is now a directory; config.json carries a stable mount id.
  The volume store (~/.bdrive/volumes/<mount-id>/) and registry are keyed
  by that id — never the folder path — so renames/moves are free.
- The daemon re-reads the project config each tick and exits cleanly
  (propagating nothing) when its folder vanishes; the registry self-heals
  and the next bdrive command at the new location resumes with zero
  spurious changes.

bdrive init is the front door (mnt/umnt removed; bdrive stop pauses):
- Interactive on a TTY (create new / connect existing project from the
  server's list; whole folder / shared subfolder via the include list),
  full flag bypass (--name/--project/--shared/--yes), never prompts
  without a TTY. Runs the login flow first when there is no session.
  Default server: beardrive.ai (config.DefaultServer).

Web history (revert-ready):
- Hubs now always require auth; journal ops carry the signed-in account
  (user/user_name) alongside the git/OS fallback author.
- File-backed device registry: per-device name, OS, account, and the
  public IP the server observed, joined into history at read time.
- GET /api/p/<id>/history?path=|prefix= (newest first) and
  GET /api/p/<id>/blob?sha= stream any exact version — blobs are retained
  forever, so the next phase's revert is re-putting an old blob.
- UI: History button (file versions or project feed), per-folder history
  shortcut, view/download of any past version.

Tests: auth flows (callback, device-code, reset single-use, persistence,
gating), history API + device registry, folder-move survival, registry
self-heal, ops-carry-account; docs (README/SKILL/CLAUDE) updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 13:12:49 -07:00
Snow LeeandClaude Fable 5 a7bb790615 feat: multi-project sync hub, bdrive login/init onboarding, .bdrive rename
The web server (bdrive web) becomes a full sync hub, and client devices
get one-command onboarding — without ever seeing storage info or holding
cloud credentials:

- bdrive web -c config.json: server configurable from a JSON file
  (remote/addr/upload/upload_ttl/projects_db); explicit flags win.
- Hub mode: pointing bdrive web at a storage root hosts many projects,
  each under <root>/<project-id>/ (remote.Prefixed). Projects live in a
  file-backed registry (projects.json — loaded at open, rewritten
  atomically per change) with create-or-join-by-name semantics.
- Per-project APIs: /api/projects (list/create/get) and
  /api/p/<id>/{tree,file,render,download,upload/*,store/*}. The web UI
  grows a project list with per-project browsing and hash deep links.
- Browser uploads and a store proxy for syncing devices: presigned
  direct-to-storage PUTs when the backend can sign (S3 presign, GCS V4
  signed URLs; expiring, credential-free), relayed through the server
  otherwise. Journals are never presigned — only immutable blobs.
  Blobs-before-journal and one-writer-per-journal invariants hold.
- https:// remote backend: a device syncs one hub project through
  /api/p/<id>/store/* — mnt/sync/daemon/log all work unchanged.
- bdrive login <url>: verify a hub and remember it as the device default
  (settings.json). bdrive init: create-or-join a project named after the
  folder (--name/--project override), write .bdrive, seed a starter
  .bdriveignore, mount, and start the daemon — one command per project.
- Hard-break rename: .beardrive->.bdrive, .beardriveignore->.bdriveignore,
  ~/.beardrive->~/.bdrive, BEARDRIVE_HOME->BDRIVE_HOME, temp/conflict
  prefixes; old names are no longer read.
- Tests: presigning, project registry persistence, store API validation
  and gating, project isolation over live HTTP, browser upload flows, and
  two-device convergence through a hub (incl. read-only pull-only mode).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 07:13:00 -07:00
Snow LeeandClaude Fable 5 fafb7fe24d feat: fold the web viewer into the CLI as bdrive web
One binary instead of two: cmd/bdrive-web becomes the `web` subcommand
(same flags and positional folder-or-URL argument). Measured cost of
carrying the webapp in the CLI: +1.4 MB on a ~56 MB binary (~2.4%) —
the cloud SDKs dominate either way. Drops the second goreleaser build
and the separate go install path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHEUaYfFHhmDvqLYw74Ehz
2026-07-07 16:06:26 -07:00
Snow LeeandClaude Fable 5 1ac128db2c rename: sfs -> BearDrive everywhere; CLI becomes bdrive
Product and project are BearDrive; the CLI binary is bdrive (bdrive mnt,
bdrive sync, ...), the web viewer is bdrive-web. All conventions follow
the full name: .beardrive settings file, .beardriveignore, ~/.beardrive
home, BEARDRIVE_HOME, .beardrive-conflict-* / .beardrive-tmp-* files.
Plugin/skill/marketplace renamed to beardrive (/beardrive:mount).

Module path is now github.com/runbear-io/beardrive — merge only after
renaming the GitHub repo, or go install breaks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHEUaYfFHhmDvqLYw74Ehz
2026-07-07 15:02:40 -07:00
Snow LeeandClaude Fable 5 7f098b3ff5 rename: brand the product BearDrive, keep sfs as the CLI
BearDrive is the product and repo brand (open source + future managed
cloud); sfs stays the CLI command, and .sfs/.sfsignore/~/.sfs conventions
are unchanged, so nothing breaks. Brew formula becomes
runbear-io/tap/beardrive (still installs the sfs binary).

Not included here (need the GitHub repo rename first): the Go module
path and repo URLs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHEUaYfFHhmDvqLYw74Ehz
2026-07-07 14:52:32 -07:00
Snow LeeandClaude Fable 5 2d29ff8aac feat: web viewer, project-local config, selective sync, Claude Code plugin
- sfs-web (cmd/sfs-web, internal/webapp): read-only Obsidian-style web UI
  serving a local folder (default) or an sfs remote; markdown rendering
  with [[wikilinks]], task lists and tables, file downloads with ETags,
  per-file provenance from the journals; added to goreleaser builds
- .sfs project file (internal/config): per-folder volume/remote/include
  settings that travel with the folder, win over the global registry, and
  never sync; daemon picks up edits live
- .sfsignore + include lists (internal/syncer): gitignore-style selective
  sync with ! re-includes, applied symmetrically in scan and materialize;
  newly ignored files stop syncing without being deleted anywhere
- Claude Code plugin (plugin/, .claude-plugin/): sfs skill, /sfs:mount and
  /sfs:status commands, turn-boundary sync hooks (blocking pull on prompt,
  async push on stop); installable via the repo's marketplace manifest
- CLAUDE.md and .claude project settings for Claude Code development

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHEUaYfFHhmDvqLYw74Ehz
2026-07-07 14:05:36 -07:00