mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
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
110 lines
3.4 KiB
Go
110 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/runbear-io/beardrive/internal/syncer"
|
|
)
|
|
|
|
// urlCmd prints a file's internal hub link — the viewer URL colleagues with
|
|
// project access open after signing in. The counterpart to `bdrive share`:
|
|
// share mints a PUBLIC link, url points at the permission-walled viewer.
|
|
func urlCmd() *cobra.Command {
|
|
var doSync bool
|
|
c := &cobra.Command{
|
|
Use: "url [path]",
|
|
Short: "Print a file's internal hub link (sign-in + membership required to view)",
|
|
Long: `Print the hub viewer URL for a file or folder in a bdrive project — the
|
|
link to hand teammates: it requires signing in to the hub and membership in
|
|
the project's organization, and always shows the latest synced content.
|
|
With no path (or "."), prints the project's home page URL.
|
|
|
|
This is the internal counterpart to "bdrive share": share mints a public
|
|
URL anyone can open; url points at the permission-walled viewer.
|
|
|
|
The link resolves once the file has synced — the daemon usually pushes
|
|
within seconds of saving; --sync pushes right now instead of waiting.
|
|
|
|
Computed locally from the folder's config; no network unless --sync.`,
|
|
Example: ` bdrive url wiki/report.md
|
|
bdrive url wiki/report.md --sync # push first, so the link works immediately
|
|
bdrive url wiki/ # the folder's listing
|
|
bdrive url # the project's home page`,
|
|
Args: cobra.MaximumNArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
target := "."
|
|
if len(args) == 1 {
|
|
target = args[0]
|
|
}
|
|
abs, err := filepath.Abs(target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// findProject walks up from a directory; start at the target
|
|
// itself only when it is one.
|
|
start := filepath.Dir(abs)
|
|
if fi, err := os.Stat(abs); err == nil && fi.IsDir() {
|
|
start = abs
|
|
}
|
|
root, proj, err := findProject(start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rel, err := filepath.Rel(root, abs)
|
|
if err != nil || strings.HasPrefix(rel, "..") {
|
|
return fmt.Errorf("%s is outside the project at %s", abs, root)
|
|
}
|
|
rel = filepath.ToSlash(rel)
|
|
if rel != "." {
|
|
// A link to something the project doesn't sync would 404 for
|
|
// everyone — refuse it here instead.
|
|
filter, err := syncer.LoadFilter(root, proj.Include)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if filter.Skip(rel) {
|
|
return fmt.Errorf("%s is not synced (ignored, or outside the project's shared scope)", rel)
|
|
}
|
|
}
|
|
server, projectID, err := splitHubRemote(proj.Remote)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if doSync {
|
|
sess, _, err := openSession(cmd.Context(), root, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closeSession(sess)
|
|
if _, err := sess.Cycle(cmd.Context()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
link := server + "/" + projectID
|
|
if rel != "." {
|
|
link += "/" + encodePathSegments(rel)
|
|
}
|
|
fmt.Fprintln(cmd.OutOrStdout(), link)
|
|
return nil
|
|
},
|
|
}
|
|
c.Flags().BoolVar(&doSync, "sync", false, "run a sync first so a just-created file is pushed and the link resolves immediately")
|
|
return c
|
|
}
|
|
|
|
// encodePathSegments percent-encodes each path segment while keeping the
|
|
// "/" separators literal, matching the viewer's routing (no %2F).
|
|
func encodePathSegments(p string) string {
|
|
segs := strings.Split(p, "/")
|
|
for i, s := range segs {
|
|
segs[i] = url.PathEscape(s)
|
|
}
|
|
return strings.Join(segs, "/")
|
|
}
|