Skip to content

Commit b9d75d3

Browse files
committed
fix(scripts): Add cli mode
1 parent 0f8aede commit b9d75d3

File tree

3 files changed

+283
-20
lines changed

3 files changed

+283
-20
lines changed

README.md

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ A beautiful terminal-based time tracker built with Go and the Charmbracelet ecos
1010

1111
- 🎯 **Retrospective time tracking** - Log completed tasks with automatic duration calculation
1212
- 🎨 **Beautiful TUI interface** - Styled with Charmbracelet's Lipgloss
13-
-**Simple workflow** - Start → Work → Complete tasks → View reports
13+
-**CLI and TUI modes** - Quick command-line operations or interactive interface
1414
- 📊 **Real-time summaries** - Work/break/total time with project breakdowns
1515
- 🔄 **Task extension** - Extend the last task if you're still working on it
1616
- 💾 **Persistent storage** - JSON-based data storage with automatic backups
1717
- 🎨 **Color-coded activities** - Visual distinction between work, breaks, and ignored time
18+
- 📋 **Project categorization** - Automatic parsing of "Project: Task" format
1819

1920
## 🚀 Installation
2021

@@ -48,7 +49,40 @@ go install
4849

4950
## 🎮 Usage
5051

51-
### Key Commands
52+
### Command Line Interface (CLI)
53+
54+
For quick operations, use CLI commands:
55+
56+
```bash
57+
# Start your day
58+
tt -s
59+
60+
# Add completed tasks
61+
tt -a "Meeting: Standup"
62+
tt -a "Education: CKA Labs" -c "Studied networking concepts"
63+
tt -a "Lunch **" # Break task
64+
tt -a "Commuting ***" # Ignored task
65+
66+
# View today's report
67+
tt -r
68+
69+
# Extend last task to current time
70+
tt -x
71+
72+
# Show help
73+
tt -h
74+
```
75+
76+
### Terminal UI (TUI)
77+
78+
For interactive sessions, run without arguments:
79+
80+
```bash
81+
# Launch beautiful TUI interface
82+
tt
83+
```
84+
85+
### TUI Key Commands
5286

5387
#### Navigation
5488
- `↑/k, ↓/j` - Move up/down
@@ -64,26 +98,53 @@ go install
6498
- `x` - **Extend last task** (continue working on previous task)
6599
- `?` - **Toggle help** (show all commands)
66100

101+
### CLI Commands
102+
103+
```bash
104+
tt # Launch TUI interface
105+
tt -s # Start your day
106+
tt -a "task name" # Add completed task
107+
tt -a "task name" -c "comment" # Add task with comment
108+
tt -r # Show today's report
109+
tt -x # Extend last task
110+
tt -h # Show CLI help
111+
```
112+
67113
### Workflow Example
68114

69-
1. **Start your day**
70-
```
71-
Press 's' → Creates "Start" entry at current time
72-
```
115+
#### CLI Workflow (Quick & Scriptable)
116+
```bash
117+
# Start your day
118+
tt -s
73119

74-
2. **Work on tasks** (time passes naturally)
120+
# Work on tasks throughout the day...
121+
tt -a "Meeting: Daily standup"
122+
tt -a "Development: User auth" -c "Implemented JWT tokens"
123+
tt -a "Lunch **"
124+
tt -a "Code review" -c "Reviewed PR #123"
75125

76-
3. **Complete tasks as you finish them**
77-
```
78-
Press 'a' → "Meeting: Standup" → Optional comment
79-
Duration automatically calculated from last entry
80-
```
126+
# Check your progress
127+
tt -r
128+
```
81129

82-
4. **View your progress**
83-
```
84-
Press 'r' → Beautiful report with time breakdown
130+
#### TUI Workflow (Interactive & Visual)
131+
1. **Launch interface**
132+
```bash
133+
tt
85134
```
86135

136+
2. **Start your day** - Press `s`
137+
138+
3. **Work naturally** - Time passes as you focus on tasks
139+
140+
4. **Log completed work** - Press `a` when you finish something
141+
- Guided prompts for task name and optional comments
142+
- Automatic duration calculation from last entry
143+
144+
5. **Monitor progress** - Press `r` for beautiful reports
145+
146+
6. **Extend if needed** - Press `x` to continue previous task
147+
87148
### Task Types
88149

89150
The application supports three types of activities:
@@ -105,7 +166,28 @@ Meeting: Daily standup
105166

106167
## 📊 Interface Overview
107168

108-
### Main Dashboard
169+
### CLI Report Output
170+
```
171+
📊 Today's Report
172+
================
173+
174+
Work: 3h15
175+
Break: 0h45
176+
Total: 4h00
177+
178+
Projects:
179+
Meeting: 0h30
180+
Education: 1h45
181+
Development: 1h00
182+
183+
Activities:
184+
09:00-09:30 0h30 Meeting: Standup
185+
09:30-11:15 1h45 Education: CKA Labs
186+
11:15-12:00 0h45 Lunch ** [BREAK]
187+
12:00-13:00 1h00 Development: Bug fixes
188+
```
189+
190+
### TUI Main Dashboard
109191
```
110192
⏱️ Time Tracker
111193
@@ -126,7 +208,7 @@ Today's Summary:
126208
Press ? for help, q to quit
127209
```
128210

129-
### Task Completion Flow
211+
### TUI Task Completion Flow
130212
```
131213
✅ Task Completed
132214
@@ -199,7 +281,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
199281
## 🙏 Acknowledgments
200282

201283
- [Charmbracelet](https://charm.sh/) for the amazing TUI toolkit
202-
- [Conventional Commits](https://conventionalcommits.org/) for inspiration on task categorization
203284
- The Go community for excellent tooling and libraries
204285

205286
## 🐛 Issues & Support

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module timetracker
1+
module tt
22

33
go 1.24.2
44

main.go

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"encoding/json"
5+
"flag"
56
"fmt"
67
"log"
78
"os"
@@ -526,12 +527,27 @@ func (m model) mainViewRender() string {
526527

527528
// Quick stats
528529
stats := m.tracker.getTodaysStats()
529-
quickStats := fmt.Sprintf("\n%s\n%s\n%s\n%s\n",
530+
quickStats := fmt.Sprintf("\n%s\n%s\n%s\n%s",
530531
subtitleStyle.Render("Today's Summary:"),
531532
workStyle.Render(fmt.Sprintf(" Work: %s", formatDuration(stats.WorkTime))),
532533
breakStyle.Render(fmt.Sprintf(" Break: %s", formatDuration(stats.BreakTime))),
533534
subtitleStyle.Render(fmt.Sprintf(" Total: %s", formatDuration(stats.TotalTime))))
534535

536+
// Project breakdown for main view
537+
projects := m.tracker.getTodaysProjects()
538+
// Debug: Always show the projects section to see what's in it
539+
quickStats += "\n\n" + subtitleStyle.Render("Projects:")
540+
if len(projects) == 0 {
541+
quickStats += "\n" + infoStyle.Render(" No projects found")
542+
} else {
543+
for project, duration := range projects {
544+
if project == "" {
545+
project = "General"
546+
}
547+
quickStats += "\n" + workStyle.Render(fmt.Sprintf(" %s: %s", project, formatDuration(duration)))
548+
}
549+
}
550+
535551
// Message
536552
var message string
537553
if m.message != "" {
@@ -862,6 +878,19 @@ func (tt *TimeTracker) getTodaysStats() struct {
862878
}
863879
}
864880

881+
func (tt *TimeTracker) getTodaysProjects() map[string]time.Duration {
882+
activities := tt.getTodaysActivities()
883+
projects := make(map[string]time.Duration)
884+
885+
for _, activity := range activities {
886+
if activity.Type == Work {
887+
projects[activity.Project] += activity.Duration
888+
}
889+
}
890+
891+
return projects
892+
}
893+
865894
func (tt *TimeTracker) generateTodaysSummary() string {
866895
stats := tt.getTodaysStats()
867896
activities := tt.getTodaysActivities()
@@ -938,7 +967,160 @@ func formatDuration(d time.Duration) string {
938967
return fmt.Sprintf("%dh%02d", hours, minutes)
939968
}
940969

970+
func printCLIHelp() {
971+
fmt.Println("tt - Time Tracker")
972+
fmt.Println()
973+
fmt.Println("USAGE:")
974+
fmt.Println(" tt Start TUI interface")
975+
fmt.Println(" tt [command] Run command and exit")
976+
fmt.Println()
977+
fmt.Println("COMMANDS:")
978+
fmt.Println(" -s Start your day")
979+
fmt.Println(" -a \"task name\" Add completed task")
980+
fmt.Println(" -c \"comment\" Add comment (use with -a)")
981+
fmt.Println(" -r Show today's report")
982+
fmt.Println(" -x Extend last task to now")
983+
fmt.Println(" -h Show this help")
984+
fmt.Println()
985+
fmt.Println("EXAMPLES:")
986+
fmt.Println(" tt -s # Start your day")
987+
fmt.Println(" tt -a \"Meeting: Standup\"")
988+
fmt.Println(" tt -a \"Lunch **\" # Break task")
989+
fmt.Println(" tt -a \"Dev work\" -c \"Fixed login bug\"")
990+
fmt.Println(" tt -r # View today's report")
991+
fmt.Println(" tt -x # Extend last task")
992+
fmt.Println()
993+
fmt.Println("TASK TYPES:")
994+
fmt.Println(" Regular task: \"Meeting: Standup\"")
995+
fmt.Println(" Break task: \"Lunch **\"")
996+
fmt.Println(" Ignored task: \"Commuting ***\"")
997+
}
998+
999+
func printTodaysReport(tracker *TimeTracker) {
1000+
activities := tracker.getTodaysActivities()
1001+
stats := tracker.getTodaysStats()
1002+
1003+
fmt.Println("📊 Today's Report")
1004+
fmt.Println("================")
1005+
fmt.Println()
1006+
1007+
// Summary
1008+
fmt.Printf("Work: %s\n", formatDuration(stats.WorkTime))
1009+
fmt.Printf("Break: %s\n", formatDuration(stats.BreakTime))
1010+
fmt.Printf("Total: %s\n", formatDuration(stats.TotalTime))
1011+
fmt.Println()
1012+
1013+
// Projects
1014+
projects := tracker.getTodaysProjects()
1015+
if len(projects) > 0 {
1016+
fmt.Println("Projects:")
1017+
for project, duration := range projects {
1018+
if project == "" {
1019+
project = "General"
1020+
}
1021+
fmt.Printf(" %s: %s\n", project, formatDuration(duration))
1022+
}
1023+
fmt.Println()
1024+
}
1025+
1026+
// Activities
1027+
if len(activities) > 0 {
1028+
fmt.Println("Activities:")
1029+
for _, activity := range activities {
1030+
timeStr := activity.Start.Format("15:04") + "-" + activity.End.Format("15:04")
1031+
typeStr := ""
1032+
switch activity.Type {
1033+
case Break:
1034+
typeStr = " [BREAK]"
1035+
case Ignored:
1036+
typeStr = " [IGNORED]"
1037+
}
1038+
1039+
fmt.Printf(" %s %s %s%s\n",
1040+
timeStr,
1041+
formatDuration(activity.Duration),
1042+
activity.Name,
1043+
typeStr)
1044+
}
1045+
} else {
1046+
fmt.Println("No activities logged today.")
1047+
}
1048+
}
1049+
9411050
func main() {
1051+
// Parse command line flags
1052+
var (
1053+
addTask = flag.String("a", "", "Add a completed task")
1054+
startDay = flag.Bool("s", false, "Start your day")
1055+
showReport = flag.Bool("r", false, "Show today's report")
1056+
extend = flag.Bool("x", false, "Extend last task to current time")
1057+
showHelp = flag.Bool("h", false, "Show help")
1058+
comment = flag.String("c", "", "Add comment to task (use with -a)")
1059+
)
1060+
flag.Parse()
1061+
1062+
// Handle CLI commands
1063+
if *showHelp {
1064+
printCLIHelp()
1065+
return
1066+
}
1067+
1068+
// Initialize tracker for CLI operations
1069+
tracker := &TimeTracker{}
1070+
tracker.loadConfig()
1071+
tracker.loadEntries()
1072+
1073+
if *startDay {
1074+
err := tracker.addStart()
1075+
if err != nil {
1076+
fmt.Printf("Error starting day: %v\n", err)
1077+
os.Exit(1)
1078+
}
1079+
fmt.Println("✅ Day started!")
1080+
return
1081+
}
1082+
1083+
if *addTask != "" {
1084+
entry := Entry{
1085+
Timestamp: time.Now(),
1086+
Name: *addTask,
1087+
Comment: *comment,
1088+
}
1089+
1090+
err := tracker.addEntry(entry)
1091+
if err != nil {
1092+
fmt.Printf("Error adding task: %v\n", err)
1093+
os.Exit(1)
1094+
}
1095+
1096+
// Calculate and show duration
1097+
var durationMsg string
1098+
if len(tracker.entries) > 1 {
1099+
lastEntry := tracker.entries[len(tracker.entries)-2]
1100+
duration := entry.Timestamp.Sub(lastEntry.Timestamp)
1101+
durationMsg = fmt.Sprintf(" (%s)", formatDuration(duration))
1102+
}
1103+
1104+
fmt.Printf("✅ Task completed: %s%s\n", *addTask, durationMsg)
1105+
return
1106+
}
1107+
1108+
if *extend {
1109+
err := tracker.extend()
1110+
if err != nil {
1111+
fmt.Printf("Error extending task: %v\n", err)
1112+
os.Exit(1)
1113+
}
1114+
fmt.Println("✅ Task extended to current time!")
1115+
return
1116+
}
1117+
1118+
if *showReport {
1119+
printTodaysReport(tracker)
1120+
return
1121+
}
1122+
1123+
// If no CLI flags, start TUI
9421124
p := tea.NewProgram(initialModel(), tea.WithAltScreen())
9431125
if _, err := p.Run(); err != nil {
9441126
log.Fatal(err)

0 commit comments

Comments
 (0)