▍ humdrum codex / ticktock v0.0.2
license AGPL-3.0
3.7 KB raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Package menu is a native-Charm guided menu over the tock CLI. It collects
// input with huh and shells to tock; it never stores anything.
package menu

import (
	"fmt"
	"os"
	"os/exec"

	"github.com/charmbracelet/huh"

	"ticktock/internal/tui/form"
)

type action struct{ key, label string }

var actions = []action{
	{"start", "▶  start      — start an activity"},
	{"stop", "⏹  stop       — stop the running activity"},
	{"current", "⏱  current    — what's running now"},
	{"continue", "🔁 continue   — restart a recent activity"},
	{"note", "📝 note       — append a note to the last activity"},
	{"tag", "🏷  tag        — append tags to the last activity"},
	{"report", "📊 report     — time report"},
	{"list", "📅 list       — calendar view"},
	{"last", "🕘 last       — recent unique activities"},
	{"watch", "⏲  watch      — full-screen stopwatch"},
	{"analyze", "🧠 analyze    — productivity patterns"},
}

// runTock execs tock inheriting stdio so full-screen commands work and output
// prints normally.
func runTock(args []string) error {
	cmd := exec.Command("tock", args...)
	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
	return cmd.Run()
}

func input(title string, value *string) error {
	return huh.NewForm(huh.NewGroup(
		huh.NewInput().Title(title).Value(value),
	)).Run()
}

// Run shows the menu and performs the chosen action.
func Run(sug form.Suggest) error {
	var key string
	opts := make([]huh.Option[string], 0, len(actions))
	for _, a := range actions {
		opts = append(opts, huh.NewOption(a.label, a.key))
	}
	err := huh.NewForm(huh.NewGroup(
		huh.NewSelect[string]().Title("tock — pick an action").
			Options(opts...).Value(&key).Filtering(true),
	)).Run()
	if err != nil || key == "" {
		return nil // cancelled
	}

	switch key {
	case "start":
		var project, desc, tags, note string
		if err := huh.NewForm(huh.NewGroup(
			huh.NewInput().Title("project").Value(&project).Suggestions(sug.Projects),
			huh.NewInput().Title("description").Value(&desc).Suggestions(sug.Descriptions),
			huh.NewInput().Title("tags (comma-sep, blank=none)").Value(&tags).Suggestions(sug.Tags),
			huh.NewInput().Title("note (blank=none)").Value(&note),
		)).Run(); err != nil {
			return nil
		}
		if project == "" {
			return fmt.Errorf("project is required")
		}
		return runTock(startArgs(project, desc, splitTags(tags), note))
	case "stop":
		var note string
		if err := input("closing note (blank=none)", &note); err != nil {
			return nil
		}
		return runTock(stopArgs(note))
	case "continue":
		_ = runTock([]string{"last"}) // show recent for reference
		var num string
		if err := input("number to continue (blank=last)", &num); err != nil {
			return nil
		}
		return runTock(continueArgs(num))
	case "note":
		var text string
		if err := input("note text", &text); err != nil || text == "" {
			return nil
		}
		return runTock(noteArgs(text))
	case "tag":
		var tags string
		if err := input("tags (comma-sep)", &tags); err != nil {
			return nil
		}
		t := splitTags(tags)
		if len(t) == 0 {
			return nil
		}
		return runTock(tagArgs(t))
	case "report":
		var period string
		if err := huh.NewForm(huh.NewGroup(
			huh.NewSelect[string]().Title("which period?").Options(
				huh.NewOption("today", "today"),
				huh.NewOption("yesterday", "yesterday"),
				huh.NewOption("specific date", "date"),
				huh.NewOption("all", "all"),
			).Value(&period),
		)).Run(); err != nil {
			return nil
		}
		date := ""
		if period == "date" {
			if err := input("date (YYYY-MM-DD)", &date); err != nil || date == "" {
				return nil
			}
		}
		return runTock(reportArgs(period, date))
	default: // current, list, last, watch, analyze — direct passthrough
		return runTock([]string{key})
	}
}