Files
beardrive/cmd/bdrive/main.go
T
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

121 lines
3.3 KiB
Go

// bdrive is the BearDrive CLI: mount a folder, and its
// contents stay synchronized across devices and teammates through a
// BearDrive hub, with full per-file change history and offline support.
package main
import (
"fmt"
"os"
"runtime/debug"
"strings"
"github.com/spf13/cobra"
"github.com/runbear-io/beardrive/internal/config"
)
// version is set at release time via -ldflags "-X main.version=...".
// `go install …@vX.Y.Z` builds skip ldflags, so fall back to the module
// version Go stamps into the binary.
var version = "0.1.0-dev"
func resolvedVersion() string {
if version != "0.1.0-dev" {
return version
}
if bi, ok := debug.ReadBuildInfo(); ok && bi.Main.Version != "" && bi.Main.Version != "(devel)" {
return strings.TrimPrefix(bi.Main.Version, "v")
}
return version
}
func main() {
root := &cobra.Command{
Use: "bdrive",
Short: "BearDrive: a synced file system for AI agents",
Long: `bdrive — the BearDrive CLI. A mountable, offline-first, synced file
system for AI agents.
Mount any folder and BearDrive keeps it synchronized across your devices and
teammates through a BearDrive hub (bdrive web — self-hosted or BearDrive
Cloud). Every change is journaled — you can always see which device and
author changed which file, and when. Files are real files on disk, so
everything keeps working offline; changes sync when the remote is reachable.`,
SilenceUsage: true,
Version: resolvedVersion(),
}
root.SetVersionTemplate("beardrive {{.Version}}\n")
root.AddCommand(
loginCmd(),
logoutCmd(),
initCmd(),
shareCmd(),
urlCmd(),
stopCmd(),
scopeCmd(),
forgetCmd(),
syncCmd(),
readLogCmd(),
hookApproveCmd(),
hooksCmd(),
skillCmd(),
statusCmd(),
logCmd(),
restoreCmd(),
exportCmd(),
importCmd(),
webCmd(),
whoamiCmd(),
daemonCmd(),
versionCmd(),
)
if err := root.Execute(); err != nil {
os.Exit(1)
}
}
func versionCmd() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Print the bdrive version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("beardrive", resolvedVersion())
},
}
}
func whoamiCmd() *cobra.Command {
return &cobra.Command{
Use: "whoami",
Short: "Show this device's identity used in change tracking",
RunE: func(cmd *cobra.Command, args []string) error {
dev, err := config.LoadDevice()
if err != nil {
return err
}
home, err := config.Home()
if err != nil {
return err
}
fmt.Printf("device id: %s\n", dev.ID)
fmt.Printf("device name: %s\n", dev.Name)
settings, serr := config.LoadSettings()
if serr != nil {
fmt.Printf("account: unknown — cannot read settings: %v\n", serr)
} else if settings.Email != "" {
who := settings.Email
if settings.Name != "" {
who = settings.Name + " <" + settings.Email + ">"
}
fmt.Printf("account: %s (from `bdrive login`; changes are attributed to this)\n", who)
fmt.Printf("author: %s (git/OS fallback, used only when signed out)\n", dev.Author)
} else {
fmt.Printf("account: not signed in — changes are attributed to the author below (run `bdrive login`)\n")
fmt.Printf("author: %s (detected from git config / OS user)\n", dev.Author)
}
fmt.Printf("beardrive home: %s\n", home)
return nil
},
}
}