Wire manual Y menu and CLI sync for issues and work items, persist per-project credentials, and keep local edits until an explicit push. Co-authored-by: Cursor <cursoragent@cursor.com>
153 lines
3.9 KiB
Go
153 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
"task/internal/command"
|
|
"task/internal/config"
|
|
"task/internal/project"
|
|
"task/internal/ticket"
|
|
"task/internal/ui"
|
|
"task/internal/version"
|
|
"task/internal/ytsync"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) > 1 {
|
|
switch os.Args[1] {
|
|
case "version", "--version", "-v":
|
|
fmt.Printf("%s %s\n", config.CliName, version.BuildVersion)
|
|
return
|
|
case "help", "--help", "-h":
|
|
printHelp()
|
|
return
|
|
case "sync":
|
|
if err := runSync(os.Args[2:]); err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
return
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
|
|
printHelp()
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
if err := run(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run() error {
|
|
cfg, err := config.LoadOrCreate()
|
|
if err != nil {
|
|
return fmt.Errorf("config: %w", err)
|
|
}
|
|
|
|
store := ticket.NewJSONStore(cfg.DataDir)
|
|
timeStore := ticket.NewJSONTimeStore(cfg.DataDir)
|
|
projects := project.NewDirStore(cfg.DataDir)
|
|
if err := projects.Ensure(cfg.ActiveProject); err != nil {
|
|
return fmt.Errorf("ensure project: %w", err)
|
|
}
|
|
|
|
history, err := command.OpenHistory(command.HistoryPath(cfg.DataDir), store, timeStore)
|
|
if err != nil {
|
|
return fmt.Errorf("history: %w", err)
|
|
}
|
|
model := ui.New(cfg, store, projects, timeStore, history)
|
|
prog := tea.NewProgram(model, tea.WithAltScreen())
|
|
if _, err := prog.Run(); err != nil {
|
|
return fmt.Errorf("tui: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func runSync(args []string) error {
|
|
cfg, err := config.LoadOrCreate()
|
|
if err != nil {
|
|
return fmt.Errorf("config: %w", err)
|
|
}
|
|
projectID := cfg.ActiveProject
|
|
action := ""
|
|
for i := 0; i < len(args); i++ {
|
|
switch args[i] {
|
|
case "pull", "push":
|
|
if action != "" {
|
|
return fmt.Errorf("specify only one of pull/push")
|
|
}
|
|
action = args[i]
|
|
case "--project", "-p":
|
|
if i+1 >= len(args) {
|
|
return fmt.Errorf("%s requires a project id", args[i])
|
|
}
|
|
i++
|
|
projectID = strings.TrimSpace(args[i])
|
|
case "--help", "-h":
|
|
fmt.Printf("Usage: %s sync <pull|push> [--project ID]\n", config.CliName)
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("unknown sync argument: %s (use pull or push)", args[i])
|
|
}
|
|
}
|
|
if action == "" {
|
|
return fmt.Errorf("usage: %s sync <pull|push> [--project ID]", config.CliName)
|
|
}
|
|
if projectID == "" {
|
|
return fmt.Errorf("project id required")
|
|
}
|
|
|
|
store := ticket.NewJSONStore(cfg.DataDir)
|
|
timeStore := ticket.NewJSONTimeStore(cfg.DataDir)
|
|
projects := project.NewDirStore(cfg.DataDir)
|
|
if err := projects.Ensure(projectID); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch action {
|
|
case "push":
|
|
res, err := ytsync.PushProject(context.Background(), projects, store, timeStore, projectID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("pushed %s: tickets +%d ~%d, time %d\n",
|
|
projectID, res.Created, res.Updated, res.TimePushed)
|
|
default:
|
|
res, err := ytsync.PullProject(context.Background(), projects, store, timeStore, projectID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("pulled %s: tickets +%d ~%d, time %d (%d issues)\n",
|
|
projectID, res.Created, res.Updated, res.TimePulled, res.Total)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func printHelp() {
|
|
fmt.Printf(`%s — offline-first terminal task manager
|
|
|
|
Usage:
|
|
%s Start the TUI
|
|
%s sync pull [-p ID] Pull issues from YouTrack
|
|
%s sync push [-p ID] Push local changes to YouTrack
|
|
%s version Print build version
|
|
%s help Show this help
|
|
|
|
Env:
|
|
TASK_MASTER_HOME Override home for config/data (dev/demo sandbox)
|
|
TASK_MASTER_DATA_DIR Override data directory only
|
|
|
|
YouTrack:
|
|
In the TUI: p → project → e to set remote URL, token, project key.
|
|
Then Y → Pull or Push (manual only; edits stay local until you push).
|
|
|
|
`, config.CliName, config.CliName, config.CliName, config.CliName, config.CliName, config.CliName)
|
|
}
|