Add tm_server with SQLite, Svelte UI, and task-master TUI sync.

Introduces the web backend (auth, RBAC, project tokens, revisions),
YouTrack-or-taskmaster backend choice in the TUI, and a first-pass
embedded Svelte UI. The frontend is functional but needs a major rework.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-27 22:23:11 +01:00
co-authored by Cursor
parent 98d2253f44
commit 4adfcdb95a
67 changed files with 7188 additions and 129 deletions
+66 -1
View File
@@ -10,6 +10,12 @@ import (
const metaFileName = "meta.json"
const (
BackendNone = "none"
BackendYouTrack = "youtrack"
BackendTaskMaster = "taskmaster"
)
// YouTrack holds remote sync settings for a project.
type YouTrack struct {
BaseURL string `json:"baseUrl"`
@@ -34,9 +40,59 @@ func (y YouTrack) IssueQuery() string {
return "project: " + strings.TrimSpace(y.ProjectKey)
}
// TaskMaster holds sync settings for a remote tm_server project.
type TaskMaster struct {
BaseURL string `json:"baseUrl"`
Token string `json:"token"`
ProjectID string `json:"projectId"`
}
// Configured reports whether task-master sync can run.
func (t TaskMaster) Configured() bool {
return strings.TrimSpace(t.BaseURL) != "" &&
strings.TrimSpace(t.Token) != "" &&
strings.TrimSpace(t.ProjectID) != ""
}
// Meta is per-project metadata stored beside tickets.json.
type Meta struct {
YouTrack *YouTrack `json:"youtrack,omitempty"`
Backend string `json:"backend,omitempty"` // none|youtrack|taskmaster
YouTrack *YouTrack `json:"youtrack,omitempty"`
TaskMaster *TaskMaster `json:"taskmaster,omitempty"`
}
// EffectiveBackend returns the sync backend kind, with legacy youtrack inference.
func (m Meta) EffectiveBackend() string {
b := strings.TrimSpace(strings.ToLower(m.Backend))
switch b {
case BackendYouTrack, BackendTaskMaster, BackendNone:
return b
}
if m.YouTrack != nil && m.YouTrack.Configured() {
return BackendYouTrack
}
if m.TaskMaster != nil && m.TaskMaster.Configured() {
return BackendTaskMaster
}
if m.YouTrack != nil {
return BackendYouTrack
}
if m.TaskMaster != nil {
return BackendTaskMaster
}
return BackendNone
}
// SyncConfigured reports whether the active backend is fully set up.
func (m Meta) SyncConfigured() bool {
switch m.EffectiveBackend() {
case BackendYouTrack:
return m.YouTrack != nil && m.YouTrack.Configured()
case BackendTaskMaster:
return m.TaskMaster != nil && m.TaskMaster.Configured()
default:
return false
}
}
func (s *DirStore) metaPath(id string) string {
@@ -63,6 +119,12 @@ func (s *DirStore) LoadMeta(id string) (Meta, error) {
if err := json.Unmarshal(data, &meta); err != nil {
return Meta{}, fmt.Errorf("parse meta %s: %w", id, err)
}
if meta.Backend == "" && meta.YouTrack != nil {
meta.Backend = BackendYouTrack
}
if meta.Backend == "" && meta.TaskMaster != nil {
meta.Backend = BackendTaskMaster
}
return meta, nil
}
@@ -74,6 +136,9 @@ func (s *DirStore) SaveMeta(id string, meta Meta) error {
if err := s.Ensure(id); err != nil {
return err
}
if meta.Backend == "" {
meta.Backend = meta.EffectiveBackend()
}
path := s.metaPath(id)
data, err := json.MarshalIndent(meta, "", " ")
if err != nil {