mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
a00f7cd1a9
commit
34afef17cc
@@ -135,7 +135,7 @@ hub's own storage, never something a syncing client points at directly:
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `bdrive login [server-url]` | Sign this device in (browser flow; `--device` for headless; default server beardrive.ai — the managed cloud, free personal workspace on signup; pass your hub URL to self-host). Switch hubs with `bdrive login <new-url>` |
|
||||
| `bdrive login [server-url]` | Sign this device in (browser flow; `--device` forces the code flow, and shells without a TTY fall back to it automatically; default server beardrive.ai — the managed cloud, free personal workspace on signup; pass your hub URL to self-host). Switch hubs with `bdrive login <new-url>` |
|
||||
| `bdrive logout` | Sign this device out — clear the saved token/account (`--forget` also drops the remembered server) |
|
||||
| `bdrive init [folder]` | Create/connect a project and start syncing — interactive on a TTY, flags (`--name/--project/--shared/--yes`) for scripts; re-run to resume |
|
||||
| `bdrive stop [folder]` | Stop syncing (files stay; `bdrive init` resumes) |
|
||||
@@ -148,7 +148,8 @@ hub's own storage, never something a syncing client points at directly:
|
||||
| `bdrive status [folder]` | Projects, daemon state, pending changes |
|
||||
| `bdrive log [folder] [-p path] [-n N]` | Change history: account, device, time, file |
|
||||
| `bdrive web [folder \| storage-root-url]` | Web server: viewer (rendered markdown, downloads, history), uploads, multi-project sync hub |
|
||||
| `bdrive whoami` | Device identity used in change tracking |
|
||||
| `bdrive whoami` | Signed-in account and device identity used in change tracking |
|
||||
| `bdrive version` | Print the version (also `bdrive --version`) |
|
||||
|
||||
## Project files
|
||||
|
||||
|
||||
+12
-1
@@ -156,7 +156,18 @@ the folder was renamed or moved.`,
|
||||
if shared != "" {
|
||||
fmt.Printf(" syncing: ./%s only\n", shared)
|
||||
}
|
||||
return startSync(cmd.Context(), folder, proj, foreground, 3*time.Second, 10*time.Second)
|
||||
if err := startSync(cmd.Context(), folder, proj, foreground, 3*time.Second, 10*time.Second); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(`
|
||||
done — the daemon now keeps this folder in sync automatically.
|
||||
|
||||
next steps:
|
||||
connect another device or teammate: bdrive init --project %s
|
||||
see who changed what: bdrive log
|
||||
share a file by public URL: bdrive share <file>
|
||||
`, p.ID)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
c.Flags().StringVar(&projectID, "project", "", "connect an existing project by id (p-xxxxxxxx)")
|
||||
|
||||
+20
-1
@@ -17,6 +17,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/runbear-io/beardrive/internal/config"
|
||||
@@ -163,6 +164,13 @@ func runLogin(server string, cfg serverConfig, useDevice bool) error {
|
||||
if loginPath == "" {
|
||||
loginPath = "/auth/cli"
|
||||
}
|
||||
// Headless shells (agents, CI, SSH) can't complete the loopback-callback
|
||||
// flow — the browser would open nowhere and the CLI would hang. Fall back
|
||||
// to the device-code flow automatically instead of waiting.
|
||||
if !useDevice && !isatty.IsTerminal(os.Stdin.Fd()) && !isatty.IsCygwinTerminal(os.Stdin.Fd()) {
|
||||
fmt.Println("no interactive terminal detected — using the device-code sign-in flow")
|
||||
useDevice = true
|
||||
}
|
||||
var token string
|
||||
var user serverUser
|
||||
var err error
|
||||
@@ -170,6 +178,10 @@ func runLogin(server string, cfg serverConfig, useDevice bool) error {
|
||||
token, user, err = deviceCodeLogin(server)
|
||||
} else {
|
||||
token, user, err = browserLogin(server, loginPath)
|
||||
if errors.Is(err, errNoBrowser) {
|
||||
fmt.Println("could not open a browser — switching to the device-code sign-in flow")
|
||||
token, user, err = deviceCodeLogin(server)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -238,6 +250,10 @@ func deviceName() string {
|
||||
return "cli"
|
||||
}
|
||||
|
||||
// errNoBrowser signals that the loopback flow can't proceed because no local
|
||||
// browser could be opened; the caller falls back to the device-code flow.
|
||||
var errNoBrowser = errors.New("no local browser")
|
||||
|
||||
// browserLogin runs the loopback-callback flow.
|
||||
func browserLogin(server, loginPath string) (string, serverUser, error) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
@@ -272,8 +288,11 @@ func browserLogin(server, loginPath string) (string, serverUser, error) {
|
||||
fmt.Println("opening your browser to sign in (sign up there if you don't have an account):")
|
||||
fmt.Println(" " + loginURL)
|
||||
if err := openBrowser(loginURL); err != nil {
|
||||
fmt.Println("could not open a browser — open the URL above manually, or rerun with --device")
|
||||
// The loopback callback only works from a browser on this machine, so
|
||||
// a URL the user pastes elsewhere would dead-end — use the code flow.
|
||||
return "", serverUser{}, errNoBrowser
|
||||
}
|
||||
fmt.Println("waiting for the browser sign-in… (no browser here? Ctrl-C and run `bdrive login --device`)")
|
||||
|
||||
select {
|
||||
case code := <-codeCh:
|
||||
|
||||
+13
-1
@@ -28,7 +28,9 @@ 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: version,
|
||||
}
|
||||
root.SetVersionTemplate("beardrive {{.Version}}\n")
|
||||
root.AddCommand(
|
||||
loginCmd(),
|
||||
logoutCmd(),
|
||||
@@ -77,7 +79,17 @@ func whoamiCmd() *cobra.Command {
|
||||
}
|
||||
fmt.Printf("device id: %s\n", dev.ID)
|
||||
fmt.Printf("device name: %s\n", dev.Name)
|
||||
fmt.Printf("author: %s\n", dev.Author)
|
||||
if settings, _ := config.LoadSettings(); 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
|
||||
},
|
||||
|
||||
@@ -134,8 +134,6 @@ func Run(folder string, scanInterval, remoteInterval time.Duration) error {
|
||||
}
|
||||
defer os.Remove(PidPath(volDir))
|
||||
|
||||
settings, _ := config.LoadSettings()
|
||||
|
||||
log.Printf("daemon started: folder=%s mount=%s volume=%s remote=%q device=%s(%s) scan=%s sync=%s",
|
||||
folder, proj.ID, proj.Volume, proj.Remote, dev.Name, dev.ID, scanInterval, remoteInterval)
|
||||
|
||||
@@ -191,6 +189,10 @@ func Run(folder string, scanInterval, remoteInterval time.Duration) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Re-read settings each tick too, so a login/logout/account switch
|
||||
// after the daemon started is reflected in op authorship — otherwise
|
||||
// a long-lived daemon stamps every change with a stale identity.
|
||||
settings, _ := config.LoadSettings()
|
||||
sess := &syncer.Session{Folder: folder, MountID: proj.ID, Store: st, Device: dev, Account: settings}
|
||||
if doRemote {
|
||||
sess.Backend = be
|
||||
|
||||
@@ -23,7 +23,7 @@ Use this skill whenever the user is working with the `bdrive` CLI: initializing
|
||||
| Mounts + daemon + pending state | `bdrive status [<folder>]` |
|
||||
| Change history | `bdrive log [<folder>] [-p path] [-n N]` |
|
||||
| This device's identity | `bdrive whoami` |
|
||||
| Sign this device in (once per device) | `bdrive login [url]` — bare form targets BearDrive Cloud (beardrive.ai): signing up there auto-creates a free personal workspace, no questions asked. Self-hosting teams pass their hub URL instead. Opens the sign-in page in a browser (sign-up available there); the terminal completes on its own and stores a per-device token. `--device` prints a code to approve from any browser (SSH/headless); `--status` shows server + account. Password reset: "Forgot password?" on the sign-in page (emailed via the server's SMTP config, or the link appears in the server log). **Switch hubs** with `bdrive login <new-url>`, then re-run `bdrive init` in each folder. |
|
||||
| Sign this device in (once per device) | `bdrive login [url]` — bare form targets BearDrive Cloud (beardrive.ai): signing up there auto-creates a free personal workspace, no questions asked. Self-hosting teams pass their hub URL instead. Opens the sign-in page in a browser (sign-up available there); the terminal completes on its own and stores a per-device token. `--device` prints a code to approve from any browser (SSH/headless), and login falls back to that code flow automatically when there is no TTY (agent shells, CI) or no browser opens; `--status` shows server + account. Password reset: "Forgot password?" on the sign-in page (emailed via the server's SMTP config, or the link appears in the server log). **Switch hubs** with `bdrive login <new-url>`, then re-run `bdrive init` in each folder. |
|
||||
| Sign this device out | `bdrive logout` — clears the saved token + account (folders untouched); `--forget` also drops the remembered server. The device token stays valid server-side until it expires — revoke it from the hub's device list to be sure. |
|
||||
| Link a synced file for teammates | `bdrive url <file>` — prints the file's hub viewer URL (sign-in + project membership required; always the latest content). Computed locally, no network; `--sync` pushes first so a just-created file's link resolves immediately; no arg = the project home page. **After creating a shareable artifact (.md/.html/.csv/report/plan) in the shared folder, include this link in your reply** so teammates can open it. |
|
||||
| Share a synced file publicly by URL | `bdrive share <file>` — prints a link anyone can open (HTML renders as a page, markdown rendered, PDFs inline; sandboxed; always the latest content; no account needed). `--expires 24h` for self-destructing links; `--list` / `--revoke <token-or-url>` to manage. Put generated reports in the shared folder, sync, then share. |
|
||||
@@ -484,12 +484,14 @@ History is content-addressed — overwritten and deleted files are still in the
|
||||
```
|
||||
device id: d380dea58598
|
||||
device name: macbook
|
||||
author: snow@runbear.io
|
||||
account: Snow <snow@runbear.io> (from `bdrive login`; changes are attributed to this)
|
||||
author: snow@runbear.io (git/OS fallback, used only when signed out)
|
||||
beardrive home: /Users/snow/.bdrive
|
||||
```
|
||||
|
||||
- **device id** — random 12-hex, generated on first run, persisted to `~/.bdrive/device.json`.
|
||||
- **device name** — hostname (without `.local`).
|
||||
- **account** — the signed-in hub account; changes are attributed to it in `bdrive log` and hub history. When signed out, the author fallback is used instead.
|
||||
- **author** — `git config user.email` if present, else `$USER@<hostname>`.
|
||||
|
||||
To change name/author, edit `~/.bdrive/device.json` and restart the daemon (`bdrive stop`/`bdrive init`).
|
||||
|
||||
@@ -9,7 +9,7 @@ One binary, `bdrive` — the CLI, the sync daemon, and the web server.
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `bdrive login [server-url]` | Sign this device in. Browser flow; `--device` for headless. Default server is beardrive.ai — the managed cloud, free personal workspace on signup; pass your hub URL to self-host. Switch hubs with `bdrive login <new-url>`. `--status` shows the current server and account |
|
||||
| `bdrive login [server-url]` | Sign this device in. Browser flow; `--device` forces the code flow, and shells without a TTY (agents, CI, SSH) fall back to it automatically. Default server is beardrive.ai — the managed cloud, free personal workspace on signup; pass your hub URL to self-host. Switch hubs with `bdrive login <new-url>`. `--status` shows the current server and account |
|
||||
| `bdrive logout` | Sign this device out — clear the saved token and account. `--forget` also drops the remembered server |
|
||||
| `bdrive init [folder]` | Create or connect a project and start syncing. Interactive on a TTY; flags (`--name`, `--project`, `--shared`, `--yes`) for scripts. Re-run to resume |
|
||||
| `bdrive stop [folder]` | Stop syncing. Files stay on disk; `bdrive init` resumes |
|
||||
@@ -22,8 +22,8 @@ One binary, `bdrive` — the CLI, the sync daemon, and the web server.
|
||||
| `bdrive status [folder]` | Projects, daemon state, pending changes |
|
||||
| `bdrive log [folder] [-p path] [-n N]` | Change history: account, device, time, file |
|
||||
| `bdrive web [folder \| storage-root-url]` | Web server: viewer, uploads, multi-project sync hub |
|
||||
| `bdrive whoami` | Device identity used in change tracking |
|
||||
| `bdrive version` | Version |
|
||||
| `bdrive whoami` | Signed-in account and device identity used in change tracking |
|
||||
| `bdrive version` | Version (also `bdrive --version`) |
|
||||
|
||||
## Notes on a few
|
||||
|
||||
|
||||
Reference in New Issue
Block a user