discrawl/internal/cli/share_commands.go
2026-05-05 10:07:56 +01:00

239 lines
6.5 KiB
Go

package cli
import (
"errors"
"flag"
"io"
"os"
"github.com/openclaw/discrawl/internal/config"
"github.com/openclaw/discrawl/internal/report"
"github.com/openclaw/discrawl/internal/share"
"github.com/openclaw/discrawl/internal/store"
)
func (r *runtime) runPublish(args []string) error {
fs := flag.NewFlagSet("publish", flag.ContinueOnError)
fs.SetOutput(io.Discard)
repoPath := fs.String("repo", r.cfg.Share.RepoPath, "")
remote := fs.String("remote", r.cfg.Share.Remote, "")
branch := fs.String("branch", r.cfg.Share.Branch, "")
message := fs.String("message", "", "")
readmePath := fs.String("readme", "", "")
noCommit := fs.Bool("no-commit", false, "")
push := fs.Bool("push", false, "")
withEmbeddings := fs.Bool("with-embeddings", false, "")
if err := fs.Parse(args); err != nil {
return usageErr(err)
}
if fs.NArg() != 0 {
return usageErr(errors.New("publish takes no positional arguments"))
}
opts, err := shareOptionsFromFlags(*repoPath, *remote, *branch)
if err != nil {
return err
}
if *withEmbeddings {
applyEmbeddingShareOptions(&opts, r.cfg)
}
manifest, err := share.Export(r.ctx, r.store, opts)
if err != nil {
return err
}
if *readmePath != "" {
activity, err := report.Build(r.ctx, r.store, report.Options{})
if err != nil {
return err
}
section, err := report.RenderMarkdown(activity)
if err != nil {
return err
}
if err := report.WriteReadme(*readmePath, section); err != nil {
return err
}
}
committed := false
if !*noCommit {
msg := *message
if msg == "" {
msg = "sync: discord archive"
}
committed, err = share.Commit(r.ctx, opts, msg)
if err != nil {
return err
}
}
if *push {
if err := share.Push(r.ctx, opts); err != nil {
return err
}
if err := share.MarkImported(r.ctx, r.store, manifest); err != nil {
return err
}
}
return r.print(map[string]any{
"repo_path": opts.RepoPath,
"remote": opts.Remote,
"generated_at": manifest.GeneratedAt,
"tables": manifest.Tables,
"embeddings": manifest.Embeddings,
"readme": *readmePath,
"committed": committed,
"pushed": *push,
})
}
func (r *runtime) runSubscribe(args []string) error {
fs := flag.NewFlagSet("subscribe", flag.ContinueOnError)
fs.SetOutput(io.Discard)
repoPath := fs.String("repo", "", "")
branch := fs.String("branch", "main", "")
staleAfter := fs.String("stale-after", "15m", "")
noAutoUpdate := fs.Bool("no-auto-update", false, "")
noImport := fs.Bool("no-import", false, "")
withEmbeddings := fs.Bool("with-embeddings", false, "")
if err := fs.Parse(args); err != nil {
return usageErr(err)
}
if fs.NArg() != 1 {
return usageErr(errors.New("subscribe requires one remote"))
}
remote := fs.Arg(0)
cfg, err := loadConfigOrDefault(r.configPath)
if err != nil {
return err
}
if *repoPath != "" {
cfg.Share.RepoPath = *repoPath
}
cfg.Share.Remote = remote
cfg.Share.Branch = *branch
cfg.Share.AutoUpdate = !*noAutoUpdate
cfg.Share.StaleAfter = *staleAfter
cfg.Discord.TokenSource = "none"
if err := config.Write(r.configPath, cfg); err != nil {
return configErr(err)
}
if *noImport {
return r.print(map[string]any{"config_path": r.configPath, "remote": remote, "repo_path": cfg.Share.RepoPath})
}
if err := config.EnsureRuntimeDirs(cfg); err != nil {
return configErr(err)
}
dbPath, err := config.ExpandPath(cfg.DBPath)
if err != nil {
return configErr(err)
}
r.cfg = cfg
return r.withSyncLock(func() error {
s, err := store.Open(r.ctx, dbPath)
if err != nil {
return dbErr(err)
}
defer func() { _ = s.Close() }()
expandedRepo, err := config.ExpandPath(cfg.Share.RepoPath)
if err != nil {
return configErr(err)
}
opts := share.Options{RepoPath: expandedRepo, Remote: cfg.Share.Remote, Branch: cfg.Share.Branch, Progress: r.shareProgress}
if *withEmbeddings {
applyEmbeddingShareOptions(&opts, cfg)
}
r.setSyncLockPhase("share pull")
if err := share.Pull(r.ctx, opts); err != nil {
return err
}
r.setSyncLockPhase("share import")
manifest, imported, err := share.ImportIfChanged(r.ctx, s, opts)
if err != nil {
return err
}
return r.print(map[string]any{
"config_path": r.configPath,
"repo_path": opts.RepoPath,
"remote": opts.Remote,
"generated_at": manifest.GeneratedAt,
"tables": manifest.Tables,
"embeddings": manifest.Embeddings,
"imported": imported,
})
})
}
func (r *runtime) runUpdate(args []string) error {
fs := flag.NewFlagSet("update", flag.ContinueOnError)
fs.SetOutput(io.Discard)
repoPath := fs.String("repo", r.cfg.Share.RepoPath, "")
remote := fs.String("remote", r.cfg.Share.Remote, "")
branch := fs.String("branch", r.cfg.Share.Branch, "")
withEmbeddings := fs.Bool("with-embeddings", false, "")
if err := fs.Parse(args); err != nil {
return usageErr(err)
}
if fs.NArg() != 0 {
return usageErr(errors.New("update takes no positional arguments"))
}
opts, err := shareOptionsFromFlags(*repoPath, *remote, *branch)
if err != nil {
return err
}
opts.Progress = r.shareProgress
if *withEmbeddings {
applyEmbeddingShareOptions(&opts, r.cfg)
}
r.setSyncLockPhase("share pull")
if err := share.Pull(r.ctx, opts); err != nil {
return err
}
r.setSyncLockPhase("share import")
manifest, imported, err := share.ImportIfChanged(r.ctx, r.store, opts)
if err != nil {
return err
}
return r.print(map[string]any{
"repo_path": opts.RepoPath,
"remote": opts.Remote,
"generated_at": manifest.GeneratedAt,
"tables": manifest.Tables,
"embeddings": manifest.Embeddings,
"imported": imported,
})
}
func shareOptionsFromFlags(repoPath, remote, branch string) (share.Options, error) {
expandedRepo, err := config.ExpandPath(repoPath)
if err != nil {
return share.Options{}, configErr(err)
}
if remote == "" {
return share.Options{}, configErr(errors.New("share remote is required"))
}
if branch == "" {
branch = "main"
}
return share.Options{RepoPath: expandedRepo, Remote: remote, Branch: branch}, nil
}
func applyEmbeddingShareOptions(opts *share.Options, cfg config.Config) {
opts.IncludeEmbeddings = true
opts.EmbeddingProvider = cfg.Search.Embeddings.Provider
opts.EmbeddingModel = cfg.Search.Embeddings.Model
opts.EmbeddingInputVersion = store.EmbeddingInputVersion
}
func loadConfigOrDefault(path string) (config.Config, error) {
cfg, err := config.Load(path)
if err == nil {
return cfg, nil
}
if !os.IsNotExist(err) {
return config.Config{}, configErr(err)
}
cfg = config.Default()
if err := cfg.Normalize(); err != nil {
return config.Config{}, configErr(err)
}
return cfg, nil
}