Skip to content

Commit 21fa115

Browse files
Merge pull request #76 from DylanDevelops/ravel/alternative-tracking-logic
feat: Non-directory specific tracking support
2 parents 6b18417 + a890073 commit 21fa115

16 files changed

Lines changed: 1708 additions & 177 deletions

File tree

CONTRIBUTING.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,13 @@ All user data is stored locally in:
176176
```text
177177
~/.tmpo/ # Production (default)
178178
├── tmpo.db # SQLite database
179-
└── config.yaml # Global configuration (optional)
179+
├── config.yaml # Global configuration (optional)
180+
└── projects.yaml # Global projects registry (optional)
180181
181182
~/.tmpo-dev/ # Development (when TMPO_DEV=1)
182183
├── tmpo.db # SQLite database
183-
└── config.yaml # Global configuration (optional)
184+
├── config.yaml # Global configuration (optional)
185+
└── projects.yaml # Global projects registry (optional)
184186
```
185187

186188
The database schema includes:
@@ -200,9 +202,14 @@ The database schema includes:
200202

201203
When a user runs `tmpo start`, the project name is detected in this priority order:
202204

203-
1. **`.tmporc` file** - Searches current directory and all parent directories
204-
2. **Git repository** - Uses `git rev-parse --show-toplevel` to find repo root
205-
3. **Directory name** - Falls back to current directory name
205+
1. **`--project` flag** - Explicitly specified global project (e.g., `tmpo start --project "My Project"`)
206+
2. **`.tmporc` file** - Searches current directory and all parent directories
207+
3. **Git repository** - Uses `git rev-parse --show-toplevel` to find repo root
208+
4. **Directory name** - Falls back to current directory name
209+
210+
**Global Projects:**
211+
212+
Users can create global projects with `tmpo init --global`, which stores project configurations in `~/.tmpo/projects.yaml`. These projects can be tracked from any directory using the `--project` flag.
206213

207214
This logic lives in `internal/project/detect.go`.
208215

README.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
- **🚀 Fast & Lightweight** - Built in Go, tmpo starts instantly and uses minimal resources
1616
- **🎯 Automatic Project Detection** - Detects project names from Git repos or `.tmporc` configuration files
17+
- **🌐 Global Projects** - Track time for any project from any directory without configuration files
1718
- **🎯 Milestone Tracking** - Organize time entries into sprints, releases, or project phases
1819
- **💾 Local & Private Storage** - All data stored locally in SQLite - your time tracking stays private
1920
- **📊 Rich Reporting** - View stats, export to CSV/JSON, and track hourly rates
@@ -58,6 +59,23 @@ tmpo stats
5859
tmpo milestone start "Sprint 1"
5960
```
6061

62+
### Track Projects From Anywhere
63+
64+
Create global projects to track time from any directory:
65+
66+
```bash
67+
# Create a global project
68+
tmpo init --global
69+
70+
# Track time from anywhere on your system
71+
cd /tmp
72+
tmpo start --project "Client Work" "Implementing new feature"
73+
tmpo stop
74+
75+
# View entries from anywhere
76+
tmpo log --project "Client Work"
77+
```
78+
6179
For detailed usage and all commands, see the [Usage Guide](docs/usage.md).
6280

6381
## Configuration
@@ -80,16 +98,26 @@ This opens an interactive wizard to configure:
8098

8199
### Per-Project Settings
82100

83-
Optionally create a `.tmporc` file in your project to customize settings:
101+
**Local Projects (directory-specific):**
84102

85103
```bash
86-
# Interactive form (prompts for name, rate, description)
104+
# Create a .tmporc file in your project directory
87105
tmpo init
88106

89107
# Or skip prompts and use defaults
90108
tmpo init --accept-defaults
91109
```
92110

111+
**Global Projects (track from anywhere):**
112+
113+
```bash
114+
# Create a global project you can use from any directory
115+
tmpo init --global
116+
117+
# Now track time from anywhere
118+
tmpo start --project "Project Name"
119+
```
120+
93121
See the [Configuration Guide](docs/configuration.md) for details.
94122

95123
## Feedback

cmd/entries/edit.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import (
1414
"github.com/spf13/cobra"
1515
)
1616

17-
var showAllProjects bool
17+
var (
18+
showAllProjects bool
19+
editProjectFlag string
20+
)
1821

1922
func EditCmd() *cobra.Command {
2023
cmd := &cobra.Command{
@@ -73,8 +76,8 @@ func EditCmd() *cobra.Command {
7376

7477
projectName = selectedProject
7578
} else {
76-
// Use current project
77-
detectedProject, err := project.DetectConfiguredProject()
79+
// use current project or explicit project using --project flag
80+
detectedProject, err := project.DetectConfiguredProjectWithOverride(editProjectFlag)
7881
if err != nil {
7982
ui.PrintError(ui.EmojiError, fmt.Sprintf("detecting project: %v", err))
8083
os.Exit(1)
@@ -437,6 +440,7 @@ func EditCmd() *cobra.Command {
437440
}
438441

439442
cmd.Flags().BoolVar(&showAllProjects, "show-all-projects", false, "Show project selection before entry selection")
443+
cmd.Flags().StringVarP(&editProjectFlag, "project", "p", "", "Edit entries for a specific global project")
440444

441445
return cmd
442446
}

cmd/entries/manual.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import (
1515
"github.com/spf13/cobra"
1616
)
1717

18+
var (
19+
manualProjectFlag string
20+
)
21+
1822
func getDateFormatInfo(configFormat string) (displayFormat, layout string) {
1923
switch configFormat {
2024
case "MM/DD/YYYY":
@@ -24,7 +28,6 @@ func getDateFormatInfo(configFormat string) (displayFormat, layout string) {
2428
case "YYYY-MM-DD":
2529
return "YYYY-MM-DD", "2006-01-02"
2630
default:
27-
// Default to MM-DD-YYYY
2831
return "MM-DD-YYYY", "01-02-2006"
2932
}
3033
}
@@ -49,7 +52,7 @@ func ManualCmd() *cobra.Command {
4952
// Get date format for prompts and validation
5053
dateFormatDisplay, dateFormatLayout := getDateFormatInfo(globalCfg.DateFormat)
5154

52-
defaultProject := detectProjectNameWithSource()
55+
defaultProject := detectProjectNameWithSource(manualProjectFlag)
5356

5457
var projectLabel string
5558
if defaultProject != "" {
@@ -243,6 +246,8 @@ func ManualCmd() *cobra.Command {
243246
},
244247
}
245248

249+
cmd.Flags().StringVarP(&manualProjectFlag, "project", "p", "", "Create entry for a specific global project")
250+
246251
return cmd
247252
}
248253

@@ -323,12 +328,8 @@ func normalizeAMPM(input string) string {
323328
return strings.ToUpper(input)
324329
}
325330

326-
func detectProjectNameWithSource() (string) {
327-
if cfg, _, err := settings.FindAndLoad(); err == nil && cfg != nil && cfg.ProjectName != "" {
328-
return cfg.ProjectName
329-
}
330-
331-
projectName, err := project.DetectProject()
331+
func detectProjectNameWithSource(explicitProject string) string {
332+
projectName, err := project.DetectConfiguredProjectWithOverride(explicitProject)
332333
if err != nil {
333334
return ""
334335
}

cmd/history/export.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,15 @@ func ExportCmd() *cobra.Command {
4242
var entries []*storage.TimeEntry
4343

4444
if exportMilestone != "" {
45-
projectName, err := project.DetectConfiguredProject()
46-
if err != nil {
47-
ui.PrintError(ui.EmojiError, fmt.Sprintf("detecting project: %v", err))
48-
os.Exit(1)
45+
// if --project flag is used, ensure global project config is used
46+
projectName := exportProject
47+
if projectName == "" {
48+
detectedProject, err := project.DetectConfiguredProject()
49+
if err != nil {
50+
ui.PrintError(ui.EmojiError, fmt.Sprintf("detecting project: %v", err))
51+
os.Exit(1)
52+
}
53+
projectName = detectedProject
4954
}
5055
entries, err = db.GetEntriesByMilestone(projectName, exportMilestone)
5156
} else if exportToday {

cmd/history/log.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import (
1313
)
1414

1515
var (
16-
logLimit int
17-
logProject string
18-
logMilestone string
19-
logToday bool
20-
logWeek bool
16+
logLimit int
17+
logProject string
18+
logMilestone string
19+
logToday bool
20+
logWeek bool
2121
)
2222

2323
func LogCmd() *cobra.Command {
@@ -40,10 +40,15 @@ func LogCmd() *cobra.Command {
4040
var entries []*storage.TimeEntry
4141

4242
if logMilestone != "" {
43-
projectName, err := project.DetectConfiguredProject()
44-
if err != nil {
45-
ui.PrintError(ui.EmojiError, fmt.Sprintf("detecting project: %v", err))
46-
os.Exit(1)
43+
// if --project flag is used, ensure global project config is used
44+
projectName := logProject
45+
if projectName == "" {
46+
detectedProject, err := project.DetectConfiguredProject()
47+
if err != nil {
48+
ui.PrintError(ui.EmojiError, fmt.Sprintf("detecting project: %v", err))
49+
os.Exit(1)
50+
}
51+
projectName = detectedProject
4752
}
4853
entries, err = db.GetEntriesByMilestone(projectName, logMilestone)
4954
} else if logToday {

0 commit comments

Comments
 (0)