46 Commits
Author SHA1 Message Date
Snow W. Lee (Sungwon)andGitHub b461ad4c97 Merge pull request #10 from runbear-io/feat/parallel-push-progress
perf(sync): parallel blob upload + progress bar for initial import
v0.3.1
2026-07-10 14:10:19 -07:00
Snow LeeandClaude Opus 4.8 64fa269e52 perf(sync): parallel blob upload + progress bar for initial import
The initial import was latency-bound: the push loop uploaded blobs strictly
serially, doing an Exists round-trip then a Put per blob (~2-3N sequential
round-trips against the hub).

- Upload unique blobs in parallel (errgroup, 16 in flight) — the main win.
- Drop the separate Exists round-trip: the backend's Put is idempotent and
  the hub already skips content it has (reported during signing), so the
  check was redundant.
- Session.OnProgress emits upload progress (done/total files + bytes) from
  the push phase; the CLI renders an in-place bar on a TTY and periodic
  percentage lines otherwise, wired into `bdrive init`'s initial cycle and
  `bdrive sync`. The daemon stays silent.

Multi-device convergence tests pass with -race; new TestPushProgress covers
the progress emission.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-10 10:11:54 -07:00
Snow W. Lee (Sungwon)andGitHub 9e710a2540 Merge pull request #9 from runbear-io/feat/logout-and-remote-help
feat(cli)!: hub-only — add `bdrive logout`, remove `bdrive remote`
v0.3.0
2026-07-10 09:56:17 -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 W. Lee (Sungwon)andGitHub 9fd1ab11d2 Merge pull request #8 from runbear-io/feat/db-abstraction
feat(web): pluggable metadata database (file / SQLite / Postgres-Supabase)
2026-07-10 08:53:33 -07:00
Snow LeeandClaude Opus 4.8 a9cc43354d deploy: Dockerfile + Google Cloud Run recipe
Add a CGO-free static Dockerfile (distroless) and a one-shot Cloud Run
deploy recipe (deploy/gcp-cloudrun.sh + README): single-instance hub on
Cloud Run, metadata in Cloud SQL Postgres (the new `database` backend),
blobs/journals in a GCS bucket, config + DB password in Secret Manager,
least-privilege runtime service account.

Pinned to max-instances=1 (the current build assumes one writer). Verified
end to end against a live deployment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-10 08:51:55 -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 0fa83e4532 feat(web): SQL metadata backend (SQLite + Postgres/Supabase) + conformance (phase 2)
Add a database/sql MetaStore (db_sql.go) targeting SQLite locally and
Postgres/Supabase in production through one portable schema: real tables
(accounts, tokens, auth_policy, projects, orgs, org_members, invites,
shares, devices), idempotent CREATE TABLE IF NOT EXISTS migrations at Open,
?→$N placeholder rebinding for Postgres, times as RFC3339 text, and
transactional multi-row writes (an org and its members). Pure-Go drivers
only (modernc.org/sqlite, jackc/pgx v5) so CGO-free builds keep working.

One shared conformance suite (db_conformance_test.go) runs the same
service-level operations — accounts+tokens, policy, pending/approve,
projects create-or-join/rename/delete, org roles, invite create/redeem/
uses/validity, share create/revoke/expiry, devices — then reopens the store
and asserts everything persisted. Runs against file AND sqlite always, and
postgres when BDRIVE_TEST_POSTGRES is set. Verified green on all three
against a real local Postgres.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-10 00:07:38 -07:00
Snow LeeandClaude Opus 4.8 f14601a983 refactor(web): extract MetaStore + typed repos; file backend (phase 1)
Introduce a pluggable metadata persistence layer: a MetaStore of typed
repositories (AccountRepo, ProjectRepo, OrgRepo, ShareRepo, DeviceRepo).
The five registries keep their in-memory maps + logic and now persist each
change as one record through a repo, instead of rewriting a whole JSON file
inline. The `file` backend (db_file.go) reproduces the exact on-disk JSON
formats, so a running hub loads unchanged. Open*(path) constructors stay as
thin wrappers over the file backend.

Zero behavior change: full webapp suite green; the example hub boots and
existing auth.json/orgs.json/projects.json load and sign-in works.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-10 00:01:56 -07:00
Snow W. Lee (Sungwon)andGitHub 6384401b35 Merge pull request #7 from runbear-io/feat/invite-only-signup
feat(web): invite-only signup by default; harden signup postures
2026-07-09 16:58:15 -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 W. Lee (Sungwon)andGitHub fd29352dd5 Merge pull request #6 from runbear-io/feat/native-url-paths
feat(web): native URL path routing (no hash, no %2F)
2026-07-09 14:19:42 -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 W. Lee (Sungwon)andGitHub 2e6068a413 Merge pull request #5 from runbear-io/redesign/linear-viewer
redesign(web): Linear-grade viewer/admin + file-tree UX
2026-07-09 13:41:45 -07:00
Snow LeeandClaude Opus 4.8 05e75a6b9d redesign(web): Linear-grade viewer/admin + file-tree UX
Rework the bdrive web UI away from the violet Obsidian-ish theme to a
near-black, honey-accented, hairline-crafted look in the spirit of Linear:

- Replace the mixed emoji glyphs (chevrons, clocks, palette icons, admin
  badge, history markers, share dialog) with one coherent SVG line-icon
  sprite + a svgIcon() helper; colored letter-marks for projects.
- Repoint the design tokens and rewrite style.css: near-black grounds,
  7% hairline borders, tightened type, honey spent only on active state,
  the ⌘K selection, and brand marks.
- Restyle the server-rendered auth pages and the public /s/ share shell
  to the same tokens so sign-in and the app read as one product.

File-tree behavior:
- Folders start collapsed; a lone root folder opens on load.
- Opening a file (search, wikilink, deep link) unfolds the path to it and
  scrolls the row into view. State now tracks open dirs (not collapsed),
  so it survives the periodic tree refresh.
- Plain-text/JSONL views wrap long unbreakable lines instead of
  overflowing the reading column.

Verified in the running example hub across desktop + mobile; go build,
go vet, go test ./... all green; zero horizontal page overflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-09 13:40:22 -07:00
Snow LeeandClaude Fable 5 d80548a5c3 design: two final polish nits from the confirmation review
- Auth signup domain parenthetical #888→#969696 (4.27→5.12:1, full AA).
- Sidebar org-name label gets a 44px touch height on mobile.

Design confirmation review: Visual 5 / UX 5 / Layout 5 / Accessibility 5,
overall 5.0, all objective a11y checks pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-09 00:07:17 -07:00
Snow LeeandClaude Fable 5 81547c97c8 design(round 3): last AA residuals + topbar hierarchy
Flips the final objective gate (contrast_aa) true; design-only.

- Palette match-highlight on a SELECTED row: recolor .plabel b to
  --accent-bright (#c9b3ff) → 6.19:1 on the tinted row (was accent 3.98:1).
- Auth-page secondary text .alt #8a8a8a→#969696 → 5.12:1 on the card
  (was 4.38:1).
- Polish: active-project marker uses --accent-bright to match its label;
  Upload/Download join Share/History as uniform ghost buttons so no
  secondary action shouts louder than the rest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 23:58:03 -07:00
Snow LeeandClaude Fable 5 927d77c22b design(round 2): 44px list rows, AA residuals, in-app modals
Closes the remaining design-review gaps (design-only; no flow changes).

Accessibility (the failing objective gate + residuals):
- Mobile 44px now reaches the PRIMARY tap targets: file tree rows,
  project rows, the org "Manage" button, new-project +, and folder-history
  all sized to 44px min (round 1 had only reached the header/sidebar icons).
- Residual sub-AA small text fixed: --text-faint #8a8a8a→#969696 (AA on the
  #262626 sidebar, measured 5.1); selected palette row's kind/icon lifted
  to #c4c4c4 (6.6); filled-button hover moved to --accent-press #5a3bc9 so
  white labels stay ≥4.5 on hover (7.2, was 2.9).
- Directory rows expose aria-expanded, updated on collapse toggle.

UX (the main remaining seam):
- Native prompt()/confirm() replaced with in-app modal components
  (modalPrompt / modalConfirm, destructive variant) for new project,
  rename, delete, remove member, and revoke invite/share — so every
  decision shares one visual language with the share modal and toasts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 23:48:59 -07:00
Snow LeeandClaude Fable 5 442232bab2 design(round 1): accessibility + layout pass (design-only)
Addresses the baseline design review's failing objective gates and layout
defects; no backend or flow changes.

Accessibility (was the ceiling — 3 of 4 objective gates failed):
- Contrast to AA: lifted --text-faint (#6e6e6e→#8a8a8a) and --text-dim;
  darkened --accent-dim (#7c5cd6→#6a48e0) so white button labels reach
  4.5:1; new --accent-bright (#c9b3ff) for accent text on the tinted
  active background (tree/project active rows, admin badge, invite/ghost
  buttons all lifted off sub-AA pairings). Same fix applied to the
  server-rendered auth pages, which now share the app's token values.
- Keyboard + focus: file tree, project, and org-name rows are now
  focusable (tabindex/role + Enter/Space) with a global :focus-visible
  ring; restored input focus rings on app and auth pages.
- Touch targets: every header + sidebar control is a 44x44 hit area on
  mobile; secondary file actions (History/Upload/Download) collapse under
  a "⋯ More" menu so the row still fits with zero horizontal overflow.

Layout defects:
- #meta no longer wraps to 5 lines / shoves the action buttons — single
  truncating line; #crumb truncates too.
- Long tree filenames ellipsize (label span flex:1 min-width:0) instead
  of hard-clipping.

Consistency:
- Fixed the .markdown specificity leak: admin/history/onboarding views no
  longer inherit markdown type rules (content class toggles per view), so
  the admin type scale renders as declared.
- Recolored the off-system gold Admin badge to the accent family; added a
  3-step radius token scale; swapped the mojibake-ish ▣/⛛ markers.
- Added a global [hidden] guard so explicit display rules can't override
  the hidden attribute.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 23:36:41 -07:00
Snow LeeandClaude Fable 5 b29454d073 polish(hub): safe clipboard fallback + clearer join toast
Both usability personas cleared the bar this round (admin 4.8, user 4.8;
every dimension >=4, zero blocker/major). Final touches from their notes:
copyText() guards navigator.clipboard so invite/share copy degrades to
"copy it manually" on a non-HTTPS origin instead of throwing; the join
toast now says "joined the <name> team, opening its projects" to
distinguish org from project.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 23:07:48 -07:00
Snow LeeandClaude Fable 5 40fcda7cf7 polish(hub): round-3 — mobile actions reachable, admin gating settings UI
Fixes the two round-2 majors.

User: on mobile the per-file actions (Share/History/Upload/Download) are
now icon buttons in the header — reachable again (round 2 wrongly hid
them). Search tolerates simple English plurals (ideas→idea), the
no-matches state states what search covers, the sidebar shows the hub
brand instead of the raw device name, and history-row downloads carry a
download attribute. Logged-out loads redirect to sign-in from /api/config
instead of firing 401-ing API calls.

Admin: a hub-admin "Signup & access" settings screen (⚙ Admin in the
sidebar) toggles email verification and admin approval live — persisted to
auth.json and surviving restart — while the domain allowlist and admin
list are shown read-only (deliberately server-config-owned so a browser
session can't widen access). Pending approvals live on the same screen.
Config toggles are now *bool so an explicit config value pins the setting
each boot, else the UI-saved policy stands. Invite revoke confirms; role
change re-renders the panel.

Tests: policy persistence + reload, policy API admin-only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 22:55:14 -07:00
Snow LeeandClaude Fable 5 e022998c9f polish(hub): round-2 UX — close the gap to the usability bar
Admin: top-level ⚙ settings entry (owners/admins), signup form states the
domain restriction up front, invite list shows creator + join count,
org-wide share audit shows creator/date and confirms before revoke,
self-row role/remove controls disabled to avoid footguns.

User: mobile header no longer overflows (per-file actions move to the ⌘K
palette on narrow viewports; tables/pre scroll in their own container),
empty-state copy works on mobile, share confirmation is now an explicit
"anyone with this link can view" dialog with copy/open/revoke, invite
links carry a "you've been invited" banner through login, joining opens
the joined project, brand shown as the title, logout labeled, palette
placeholder clarified to "file names".

Tests: invite use-counter + creator in the owner list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 22:39:07 -07:00
Snow LeeandClaude Fable 5 baad9e6dc2 feat(hub): signup gating, admin lifecycle, and onboarding UX
Makes a self-hosted hub safe to expose on a public URL and operable
without hand-editing JSON — addressing the blocker/major findings from
the persona usability evaluations.

Signup gating (config auth block, all optional):
- allowed_domains: signup email must match (e.g. only @runbear.io)
- require_verification: email-link activation before sign-in (reuses mailer)
- require_approval: hub admins approve new accounts (admins list)
- brand shown on the sign-in page; allow_signup:false already hid Sign up
Accounts carry a Status (active/unverified/pending); non-active accounts
cannot authenticate.

Admin lifecycle (endpoints + web UI):
- org: rename, member role change, member remove (last-owner guarded),
  invite list + revoke
- project: create (web), rename, delete (from the org panel)
- hub admins: approve/deny pending signups (sidebar bell + panel)
- org-wide public-share audit with revoke

UX: onboarding empty-state (explains invites, paste-invite + create-project)
instead of a blank sidebar; visible "Search ⌘K" button; toasts replace
blocking alert(); responsive layout with an off-canvas sidebar; joining
via #join now survives a logged-out click (token carried through login).
Web uploads are attributed to the signed-in account, not the server.
Login/signup are rate-limited per IP.

Tests: domain/verification/approval gates, auth rate limit, org+project
lifecycle, owner-only guards, invite→join→role→remove over HTTP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 22:23:27 -07:00
Snow LeeandClaude Fable 5 953db0260b feat(website): beardrive.ai landing page — "Google Drive for AI agents"
Self-contained static page (website/index.html, no build step, no
dependencies): Supabase-style structure — centered hero with announcement
pill and gradient payoff line, animated terminal demo, storage strip,
bento feature cards, closing CTA — on a honey-amber (#f5a623) accent over
near-black. Copy is built around the two value props: share files with
people (public URLs) and share context across AI agents ("your agent
knows what their agent knows"), with a Claude Code demo acting out both.
Cloud is waitlist-only (mailto; no backend yet), no pricing shown.
themes-preview.html keeps the accent-color comparison used to pick honey.
README headline and intro updated to the same positioning; GitHub repo
description updated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 21:07:40 -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 6ef1470088 feat(web): share-page footer + per-IP rate limit on /s/*
Rendered-markdown share pages get a small "Shared with BearDrive" footer
linking to the repo — the shell template only, so shared raw HTML is
still served byte-for-byte and the /s/ sandbox CSP is unchanged. All
/s/* responses now sit behind a per-IP token bucket (default 120 req/min
sustained + burst, tunable via "share_rpm" in the web config) so the one
unauthenticated surface of a hub can't be scraped into a free CDN.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 17:07:54 -07:00
Snow LeeandClaude Fable 5 85d9c795aa feat(hub): QuotaProvider seam for managed plan enforcement (OSS: unlimited)
The quota counterpart of AuthProvider: CheckWrite(org, addedBytes) runs
before every write path (browser upload via server or presign/commit,
the device-sync store proxy's sign and put), RecordUsage after success,
and CheckSeat before an invite adds a member. Denials surface as 403.
The open-source server ships only UnlimitedQuota and a nil provider
means unlimited — zero behavior change here; a managed deployment
enforces plans by swapping the provider. Unit test asserts the hooks
fire with the correct org and byte counts and that denials block
without recording usage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 17:05:52 -07:00
Snow LeeandClaude Fable 5 94ef66a4a9 feat(hub): organizations — projects walled by team membership, with invites
Every hub project now belongs to exactly one org (file-backed orgs.json,
same load-at-open + atomic-rewrite discipline as the other registries).
Membership (email -> owner|member) gates every per-project route: the
viewer APIs, uploads, history, blobs, shares management, and the device
sync store proxy; /api/projects lists only your orgs' projects, and
project names are now scoped per org. Public share links (/s/) stay
public by design.

Design choices, per the simplest-consistent rule:
- Migration: a pre-org hub sweeps all org-less projects into a "default"
  org at startup; ALL existing accounts join it (they could all see every
  project before, so anything narrower would lock someone out), oldest
  account as owner. Zero manual steps.
- An account in no org that creates a project gets a fresh org named
  after itself, so nobody is ever blocked from starting to sync.
- Invites are expiring multi-use links (default 7 days): an owner mints
  /#join/<token>, any signed-in account that opens it joins as member.
  The web UI shows the org in a sidebar footer (members on click,
  Invite button for owners).

bdrive init needed no changes: its connect-existing flow lists projects
through the now-filtered API.

Tests: OrgDB + migration units; a 403/access matrix over every
per-project route; invite mint/redeem flow; and a multi-device syncer
test proving a device holding an org-B token can neither pull org A's
files nor push into its store (sync degrades to Offline, never partial).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 17:03:48 -07:00
Snow LeeandClaude Fable 5 37f2123bbd feat(web): Cmd+K command palette — fuzzy file search, project switching, quick actions
Cmd/Ctrl+K opens a palette over the viewer: fuzzy-match any synced file
(word-start and streak-aware scoring, matched letters highlighted),
switch projects, and run context actions (share/history/download of the
open file, project history, upload, sign out). Esc, backdrop click, or
Cmd+K again closes it; arrows + Enter drive it. The overlay CSS now
re-asserts [hidden] since its display:flex otherwise overrides the UA
rule and the palette could never dismiss.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 16:53:10 -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
v0.2.2
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
Snow W. Lee (Sungwon)andGitHub d22fe71d1c Merge pull request #3 from runbear-io/docs/sfs-skill-consolidate
docs: consolidate sfs skills into one, mention in README
2026-06-17 09:21:54 -07:00
Snow LeeandClaude Opus 4.7 9f6438e073 docs: consolidate sfs skills into one, mention in README
Replaces sfs-mount / sfs-remote / sfs-status with a single sfs skill so
Claude Code loads one cohesive guide for the CLI instead of routing
between three. README gains a "Claude Code skill" section that links to
.claude/skills/sfs/SKILL.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 08:24:29 -07:00
Snow W. Lee (Sungwon)andGitHub f1180323b5 Merge pull request #1 from runbear-io/docs/sfs-skills
docs: add Claude Code skills for sfs (mount, remote, status)
2026-06-17 08:18:22 -07:00
Snow LeeandClaude Opus 4.7 db0ff4a279 docs: add Claude Code skills for sfs (mount, remote, status)
Three SKILL.md files under .claude/skills/ so Claude Code users get
agent-aware guidance for sfs:

- sfs-mount: mnt/umnt/sync flow, flags, multi-device setup
- sfs-remote: s3/gs/file URLs, AWS/GCS/MinIO/R2 credential chains
- sfs-status: status/log/whoami, daemon logs, diagnostic flow

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 08:15:57 -07:00
Snow Lee cd1834f917 Remove .omc from git v0.1.0 2026-06-12 11:46:24 -07:00
snowandClaude Fable 5 27b3adf86f fix: make daemon and state cache per-mount, not per-volume
One volume can be mounted at several folders (e.g. ./shared in multiple
repos defaulting to the same volume name). Previously the daemon pidfile
and materialization cache were keyed per volume, so mounts fought over a
single daemon and shared one folder-state cache. Key both by a mount ID
(hash of the absolute folder path); blobs, journals, and the lamport
clock stay shared per volume. Content now propagates between co-mounted
folders even offline. Also default remote polling 30s -> 10s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:46:44 -07:00
snowandClaude Fable 5 2caa09702f sfs v0.1: mountable synced file system for AI agents
- sfs mnt/umnt/sync/status/log/remote/whoami CLI (cobra)
- per-device append-only journals + content-addressed blob store
- deterministic lamport-ordered merge, LWW with conflict-copy preservation
- cloud-agnostic backends: S3, GCS, file:// (S3-compatible via AWS_ENDPOINT_URL)
- offline-first: real files on disk, journal locally, push on reconnect
- background sync daemon per mount with change tracking (device/author/time)
- macOS + Linux; Homebrew formula + goreleaser release pipeline

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:40:26 -07:00