cli: add sync push/pull/status commands

This commit is contained in:
mirivlad 2026-06-01 22:56:05 +08:00
parent 5b2cec5bcc
commit 1abe8c4fa0
1 changed files with 161 additions and 0 deletions

View File

@ -8,9 +8,11 @@ import (
"strings"
"verstak/internal/core/actions"
"verstak/internal/core/config"
"verstak/internal/core/plugins"
"verstak/internal/core/search"
"verstak/internal/core/storage"
syncsvc "verstak/internal/core/sync"
"verstak/internal/core/vault"
"verstak/internal/core/worklog"
)
@ -38,6 +40,8 @@ func main() {
runLog(os.Args[2:])
case "index":
runIndex(os.Args[2:])
case "sync":
runSync(os.Args[2:])
case "plugin":
runPlugin(os.Args[2:])
default:
@ -56,6 +60,7 @@ func usage() {
fmt.Println(" node Manage nodes")
fmt.Println(" action Manage actions")
fmt.Println(" --version Show version")
fmt.Println(" sync Sync with server (push/pull/status)")
fmt.Println(" --help Show this help")
}
@ -597,6 +602,162 @@ func runIndexRebuild(args []string) {
fmt.Printf("indexed %d nodes\n", count)
}
// --- sync ---
func runSync(args []string) {
if len(args) == 0 {
fmt.Println("verstak sync — synchronize with server")
fmt.Println()
fmt.Println("Usage: verstak sync <command> [options]")
fmt.Println()
fmt.Println("Commands:")
fmt.Println(" push Push local changes to server")
fmt.Println(" pull Pull remote changes from server")
fmt.Println(" status Show sync status")
os.Exit(0)
}
switch args[0] {
case "push":
runSyncPush(args[1:])
case "pull":
runSyncPull(args[1:])
case "status":
runSyncStatus(args[1:])
case "--help", "-h":
runSync(nil)
default:
fmt.Fprintf(os.Stderr, "Unknown sync command: %s\n", args[0])
os.Exit(1)
}
}
func openSyncDB(args []string) (*storage.DB, string) {
vaultPath, _ := stringFlag(args, "--vault")
abs, _ := filepath.Abs(vaultPath)
dbPath := filepath.Join(abs, ".verstak", "index.db")
db, err := storage.Open(dbPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Open vault: %v\n", err)
os.Exit(1)
}
return db, abs
}
func runSyncPush(args []string) {
db, abs := openSyncDB(args)
defer db.Close()
cfg, err := config.Load(abs)
if err != nil || cfg.Sync.ServerURL == "" || cfg.Sync.APIKey == "" {
fmt.Fprintln(os.Stderr, "Sync not configured. Use 'verstak sync configure' or GUI settings.")
os.Exit(1)
}
deviceID := cfg.Sync.DeviceID
if deviceID == "" {
deviceID = "cli-" + abs[:8]
}
syncSvc := syncsvc.NewService(db, deviceID)
client := syncsvc.NewClient(cfg.Sync.ServerURL, cfg.Sync.APIKey, deviceID, abs)
unpushed, err := syncSvc.GetUnpushedOps()
if err != nil {
fmt.Fprintf(os.Stderr, "Get ops: %v\n", err)
os.Exit(1)
}
if len(unpushed) == 0 {
fmt.Println("Nothing to push.")
return
}
result, err := client.Push(unpushed)
if err != nil {
fmt.Fprintf(os.Stderr, "Push failed: %v\n", err)
os.Exit(1)
}
if err := syncSvc.MarkPushed(result.Accepted); err != nil {
fmt.Fprintf(os.Stderr, "Mark pushed: %v\n", err)
os.Exit(1)
}
fmt.Printf("Pushed %d ops, accepted %d\n", len(unpushed), len(result.Accepted))
}
func runSyncPull(args []string) {
db, abs := openSyncDB(args)
defer db.Close()
cfg, err := config.Load(abs)
if err != nil || cfg.Sync.ServerURL == "" || cfg.Sync.APIKey == "" {
fmt.Fprintln(os.Stderr, "Sync not configured.")
os.Exit(1)
}
deviceID := cfg.Sync.DeviceID
if deviceID == "" {
deviceID = "cli-" + abs[:8]
}
syncSvc := syncsvc.NewService(db, deviceID)
client := syncsvc.NewClient(cfg.Sync.ServerURL, cfg.Sync.APIKey, deviceID, abs)
_, _, lastRev, _, err := syncSvc.GetState()
if err != nil {
lastRev = 0
}
result, err := client.Pull(lastRev)
if err != nil {
fmt.Fprintf(os.Stderr, "Pull failed: %v\n", err)
os.Exit(1)
}
var opIDs []string
for _, op := range result.Ops {
fmt.Printf(" %s\t%s\t%s\t%s\n", op.OpType, op.EntityType, op.EntityID, op.PayloadJSON)
opIDs = append(opIDs, op.OpID)
}
if len(opIDs) > 0 {
syncSvc.MarkApplied(opIDs)
}
fmt.Printf("Pulled %d ops (server rev: %d)\n", len(result.Ops), result.ServerRevision)
}
func runSyncStatus(args []string) {
db, abs := openSyncDB(args)
defer db.Close()
cfg, err := config.Load(abs)
configured := err == nil && cfg.Sync.ServerURL != "" && cfg.Sync.APIKey != ""
serverURL := ""
deviceID := ""
if cfg != nil {
serverURL = cfg.Sync.ServerURL
deviceID = cfg.Sync.DeviceID
}
unpushed := 0
if configured {
if deviceID == "" {
deviceID = "cli-" + abs[:8]
}
syncSvc := syncsvc.NewService(db, deviceID)
ops, _ := syncSvc.GetUnpushedOps()
unpushed = len(ops)
}
fmt.Println("Sync Status")
fmt.Println(" Configured:", configured)
fmt.Println(" Server:", serverURL)
fmt.Println(" Device:", deviceID)
fmt.Println(" Unpushed ops:", unpushed)
}
// --- plugin ---
func runPlugin(args []string) {