Add offline-first task-master TUI with tickets, board lists, and undo.

Charm-based CLI with JSON persistence, status/list filters, external editor editing, command-history undo/redo, and glamour markdown without AutoStyle OSC hangs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Skruecken
2026-10-19 10:21:48 +02:00
co-authored by Cursor
parent 4a3d0d777b
commit 24c1e146ec
37 changed files with 3259 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
package project
// Project is a lightweight workspace identified by a directory name.
type Project struct {
ID string `json:"id"`
Name string `json:"name"`
}
+65
View File
@@ -0,0 +1,65 @@
package project
import (
"fmt"
"os"
"path/filepath"
"sort"
)
// Store manages project directories under DataDir/projects.
type Store interface {
List() ([]Project, error)
Ensure(id string) error
}
// DirStore discovers projects as subdirectories of dataDir/projects.
type DirStore struct {
dataDir string
}
// NewDirStore creates a project store rooted at dataDir.
func NewDirStore(dataDir string) *DirStore {
return &DirStore{dataDir: dataDir}
}
func (s *DirStore) projectsRoot() string {
return filepath.Join(s.dataDir, "projects")
}
// List returns all project directories, sorted by name.
func (s *DirStore) List() ([]Project, error) {
root := s.projectsRoot()
entries, err := os.ReadDir(root)
if err != nil {
if os.IsNotExist(err) {
return []Project{}, nil
}
return nil, fmt.Errorf("list projects: %w", err)
}
var projects []Project
for _, e := range entries {
if !e.IsDir() {
continue
}
name := e.Name()
projects = append(projects, Project{ID: name, Name: name})
}
sort.Slice(projects, func(i, j int) bool {
return projects[i].Name < projects[j].Name
})
return projects, nil
}
// Ensure creates the project directory if it does not exist.
func (s *DirStore) Ensure(id string) error {
if id == "" {
return fmt.Errorf("project id is required")
}
dir := filepath.Join(s.projectsRoot(), id)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("create project %s: %w", id, err)
}
return nil
}