mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
* 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>
170 lines
4.5 KiB
Go
170 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/runbear-io/beardrive/internal/config"
|
|
)
|
|
|
|
// bdrive scope shows and edits the project's sync scope — the include list
|
|
// in .bdrive/config.json that `init --shared` seeds — so growing or
|
|
// shrinking what syncs never means hand-editing JSON. The daemon re-reads
|
|
// the config every tick, so changes apply within seconds.
|
|
func scopeCmd() *cobra.Command {
|
|
c := &cobra.Command{
|
|
Use: "scope",
|
|
Short: "Show or change which subfolders sync (the include list)",
|
|
Long: `Show or change the project's sync scope: the include list in
|
|
.bdrive/config.json, as set by init --shared. An empty list means the whole
|
|
folder syncs. Run from the mount root; the daemon picks changes up within
|
|
seconds.
|
|
|
|
Removing a folder stops syncing it but deletes nothing — local files stay,
|
|
and the hub keeps everything already synced.`,
|
|
Example: ` bdrive scope # show what syncs
|
|
bdrive scope add docs # also sync ./docs
|
|
bdrive scope rm docs # stop syncing ./docs (files stay everywhere)`,
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
folder, err := absFolder(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
proj, err := mustProject(folder)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
printScope(proj)
|
|
return nil
|
|
},
|
|
}
|
|
c.AddCommand(scopeAddCmd(), scopeRmCmd())
|
|
return c
|
|
}
|
|
|
|
func scopeAddCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "add <dir>...",
|
|
Short: "Add shared subfolders to the sync scope",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
folder, err := absFolder(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
proj, err := mustProject(folder)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(proj.Include) == 0 {
|
|
return fmt.Errorf("this project syncs the whole folder; adding %s would narrow it to only that — re-run `bdrive init` with --shared if you want a scoped sync", strings.Join(args, ", "))
|
|
}
|
|
incs, err := cleanShared(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, i := range proj.Include {
|
|
seen[i] = true
|
|
}
|
|
added := 0
|
|
for _, inc := range incs {
|
|
if seen[inc] {
|
|
continue
|
|
}
|
|
if err := os.MkdirAll(filepath.Join(folder, filepath.FromSlash(strings.TrimSuffix(inc, "/"))), 0o755); err != nil {
|
|
return err
|
|
}
|
|
proj.Include = append(proj.Include, inc)
|
|
seen[inc] = true
|
|
added++
|
|
}
|
|
if added == 0 {
|
|
fmt.Println("already in the sync scope")
|
|
} else if _, err := config.SaveProject(folder, proj); err != nil {
|
|
return err
|
|
}
|
|
printScope(proj)
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func scopeRmCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "rm <dir>...",
|
|
Short: "Remove shared subfolders from the sync scope (deletes nothing)",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
folder, err := absFolder(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
proj, err := mustProject(folder)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
kept, err := scopeRemove(proj.Include, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(kept) == 0 {
|
|
return fmt.Errorf("removing the last shared folder would switch to syncing the whole folder; run `bdrive stop` to stop syncing instead")
|
|
}
|
|
proj.Include = kept
|
|
if _, err := config.SaveProject(folder, proj); err != nil {
|
|
return err
|
|
}
|
|
fmt.Println("removed from the sync scope — nothing was deleted, locally or on the hub")
|
|
printScope(proj)
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
// scopeRemove drops the named dirs from the include list, matching each
|
|
// argument both literally and in normalized "dir/" form (hand-edited
|
|
// configs may hold arbitrary patterns). Unknown entries are an error.
|
|
func scopeRemove(include, args []string) ([]string, error) {
|
|
remove := map[string]bool{}
|
|
for _, a := range args {
|
|
keys := map[string]bool{strings.TrimSpace(a): true}
|
|
if norm, err := cleanShared([]string{a}); err == nil {
|
|
keys[norm[0]] = true
|
|
}
|
|
found := false
|
|
for _, i := range include {
|
|
if keys[i] {
|
|
remove[i] = true
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
return nil, fmt.Errorf("%q is not in the sync scope (see `bdrive scope`)", a)
|
|
}
|
|
}
|
|
var kept []string
|
|
for _, i := range include {
|
|
if !remove[i] {
|
|
kept = append(kept, i)
|
|
}
|
|
}
|
|
return kept, nil
|
|
}
|
|
|
|
func printScope(proj config.Project) {
|
|
if len(proj.Include) == 0 {
|
|
fmt.Println("the whole folder syncs (no include list)")
|
|
return
|
|
}
|
|
fmt.Println("syncing only:")
|
|
for _, i := range proj.Include {
|
|
fmt.Println(" ./" + strings.TrimSuffix(i, "/"))
|
|
}
|
|
}
|