From db901543f32ca69f97f9d9d74ec381fecfe34324 Mon Sep 17 00:00:00 2001 From: Dylan Ravel Date: Sun, 7 Dec 2025 01:25:29 -0800 Subject: [PATCH 1/8] Add SQLite time entry storage implementation Introduces a Database struct for managing time entries using SQLite, including initialization, entry creation, retrieval, stopping, and closing. Adds a TimeEntry model with duration and running status helpers to support time tracking functionality. --- internal/storage/db.go | 136 +++++++++++++++++++++++++++++++++++++ internal/storage/models.go | 23 +++++++ 2 files changed, 159 insertions(+) create mode 100644 internal/storage/db.go create mode 100644 internal/storage/models.go diff --git a/internal/storage/db.go b/internal/storage/db.go new file mode 100644 index 0000000..2c7f73a --- /dev/null +++ b/internal/storage/db.go @@ -0,0 +1,136 @@ +package storage + +import ( + "database/sql" + "fmt" + "os" + "path/filepath" + "time" +) + +type Database struct { + db* sql.DB +} + +func Initialize() (*Database, error) { + homeDir, err := os.UserHomeDir() + + if err != nil { + return nil, fmt.Errorf("failed to get home directory: %w", err) + } + + tmpoDir := filepath.Join(homeDir, ".tmpo") + if err := os.MkdirAll(tmpoDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create .tmpo directory: %w", err) + } + + dbPath := filepath.Join(tmpoDir, "tmpo.db") + db, err := sql.Open("sqlite3", dbPath) + + if err != nil { + return nil, fmt.Errorf("failed to open database: %w", err) + } + + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS time_entries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_name TEXT NOT NULL, + start_time DATETIME NOT NULL, + end_time DATETIME, + description TEXT + ) + `) + + if err != nil { + return nil, fmt.Errorf("failed to create table: %w", err) + } + + return &Database{db: db}, nil +} + +func (d* Database) CreateEntry(projectName, description string) (*TimeEntry, error) { + result, err := d.db.Exec( + "INSERT INTO time_entries (project_name, start_time, description) VALUES (?, ?, ?)", + projectName, + time.Now(), + description, + ) + + if err != nil { + return nil, fmt.Errorf("Failed to create entry: %w", err) + } + + id, err := result.LastInsertId() + + if err != nil { + return nil, fmt.Errorf("Failed to get last insert id: %w", err) + } + + return d.GetEntry(id) +} + +func (d* Database) GetRunningEntry() (*TimeEntry, error) { + var entry TimeEntry + var endTime sql.NullTime + + err := d.db.QueryRow(` + SELECT id, project_name, start_time, end_time, description + FROM time_entries + WHERE end_time IS NULL + ORDER BY start_time DESC + LIMIT 1 + `).Scan(&entry.ID, &entry.ProjectName, &entry.StartTime, &endTime, &entry.Description) + + if err == sql.ErrNoRows { + return nil, nil + } + + if err != nil { + return nil, fmt.Errorf("Failed to get running entry: %w", err) + } + + if endTime.Valid { + entry.EndTime = &endTime.Time + } + + return &entry, nil +} + +func (d* Database) StopEntry(id int64) error { + _, err := d.db.Exec( + "UPDATE time_entries SET end_time = ? WHERE id = ?", + time.Now(), + id, + ) + + if(err != nil) { + return fmt.Errorf("failed to stop entry: %w", err) + } + + return nil +} + +func (d* Database) GetEntry(id int64) (*TimeEntry, error) { + var entry TimeEntry + var endTime sql.NullTime + + err := d.db.QueryRow(` + SELECT id, project_name, start_time, end_time, description + FROM time_entries + WHERE id = ? + `, id).Scan(&entry.ID, &entry.ProjectName, &entry.StartTime, &endTime, &entry.Description) + + if err != nil { + return nil, fmt.Errorf("failed to get entry: %w", err) + } + + if endTime.Valid { + entry.EndTime = &endTime.Time + } + + return &entry, nil +} + +func (d* Database) Close() error { + return d.db.Close() +} \ No newline at end of file diff --git a/internal/storage/models.go b/internal/storage/models.go new file mode 100644 index 0000000..c3c5df6 --- /dev/null +++ b/internal/storage/models.go @@ -0,0 +1,23 @@ +package storage + +import "time" + +type TimeEntry struct { + ID int64 + ProjectName string + StartTime time.Time + EndTime* time.Time + Description string +} + +func (t* TimeEntry) Duration() time.Duration { + if( t.EndTime == nil) { + return time.Since(t.StartTime) + } + + return t.EndTime.Sub(t.StartTime) +} + +func (t* TimeEntry) IsRunning() bool { + return t.EndTime == nil +} \ No newline at end of file From 9a052eee357f30ddc8d95f6bb69e60415fe818d7 Mon Sep 17 00:00:00 2001 From: Dylan Ravel Date: Sun, 7 Dec 2025 09:38:08 -0800 Subject: [PATCH 2/8] Implement start command and document storage package Adds a functional 'start' command to begin time tracking for the current project, including checks for existing running sessions and project name detection. Enhances internal/storage/db.go with detailed documentation for the Database type and its methods, clarifying usage and error handling. Updates CLI descriptions for improved clarity and sets the application version. --- cmd/root.go | 28 +++------------ cmd/start.go | 78 ++++++++++++++++++++++++++++++------------ internal/storage/db.go | 56 ++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 44 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 0072352..239c539 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,23 +6,15 @@ import ( "github.com/spf13/cobra" ) -// rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "tmpo", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: + Short: "Minimal CLI time tracker for developers", + Long: `tmpo - Set the tmpo -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, +A minimal, developer-friendly time tracking tool that lives in your terminal. +Track time effortlessly with automatic project detection and simple commands.`, } -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { err := rootCmd.Execute() if err != nil { @@ -31,15 +23,5 @@ func Execute() { } func init() { - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.tmpo.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + rootCmd.Version = "0.0.1" } - - diff --git a/cmd/start.go b/cmd/start.go index a416c90..6da043d 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -2,35 +2,71 @@ package cmd import ( "fmt" + "os" + "path/filepath" + "github.com/DylanDevelops/tmpo/internal/storage" "github.com/spf13/cobra" ) -// startCmd represents the start command var startCmd = &cobra.Command{ - Use: "start", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("start called") + Use: "start [description]", + Short: "Start tracking time", + Long: `Start a new time tracking session for the current project.`, + Run: func(cmd* cobra.Command, args []string) { + db, err := storage.Initialize() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + + os.Exit(1) + } + + defer db.Close() + + running, err := db.GetRunningEntry() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + + os.Exit(1) + } + + if running != nil { + fmt.Fprintf(os.Stderr, "Error: Already tracking time for `%s\n", running.ProjectName) + fmt.Println("Use 'tmpo stop' to stop the current session first.") + + os.Exit(1) + } + + cwd, err := os.Getwd() + if err != nil { + fmt.Fprintf(os.Stderr, "Error getting current directory: %v\n", err) + + os.Exit(1) + } + + projectName := filepath.Base(cwd) + + description := "" + if len(args) > 0 { + description = args[0] + } + + entry, err := db.CreateEntry(projectName, description) + + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + + os.Exit(1) + } + + fmt.Printf("[tmpo] Started tracking time for '%s'\n", entry.ProjectName) + + if description != "" { + fmt.Printf(" Description: %s\n", description) + } }, } func init() { rootCmd.AddCommand(startCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // startCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // startCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/internal/storage/db.go b/internal/storage/db.go index 2c7f73a..5264863 100644 --- a/internal/storage/db.go +++ b/internal/storage/db.go @@ -8,10 +8,36 @@ import ( "time" ) +// Database is a thin wrapper around an *sql.DB connection pool. +// It centralizes database access for the package and provides a +// single place to implement queries, transactions, migrations and +// other persistence-related helpers. +// +// The underlying *sql.DB is safe for concurrent use, but a Database +// with a nil internal pointer is not usable — it must be initialized +// (for example via sql.Open) and closed when no longer needed. type Database struct { db* sql.DB } +// Initialize ensures the on-disk storage for the application exists, opens the +// SQLite database, and returns a Database wrapper. +// +// Specifically, Initialize: +// - determines the current user's home directory, +// - creates the directory "$HOME/.tmpo" if it does not already exist, +// - opens (or creates) the SQLite database file "$HOME/.tmpo/tmpo.db", +// - ensures the time_entries table exists with the schema: +// id INTEGER PRIMARY KEY AUTOINCREMENT, +// project_name TEXT NOT NULL, +// start_time DATETIME NOT NULL, +// end_time DATETIME, +// description TEXT +// +// On success it returns a *Database containing the opened *sql.DB. It returns +// a non-nil error if any step fails: obtaining the home directory, creating +// the directory, opening the database, or creating the table. The caller is +// responsible for closing the database when finished. func Initialize() (*Database, error) { homeDir, err := os.UserHomeDir() @@ -48,6 +74,11 @@ func Initialize() (*Database, error) { return &Database{db: db}, nil } +// CreateEntry inserts a new time entry for the specified projectName with the given +// description. The entry's start_time is set to the current time. On success it returns +// the created *TimeEntry (retrieved by querying the database for the last insert id). +// If the insert or the subsequent retrieval fails, an error wrapping the underlying +// database error is returned. func (d* Database) CreateEntry(projectName, description string) (*TimeEntry, error) { result, err := d.db.Exec( "INSERT INTO time_entries (project_name, start_time, description) VALUES (?, ?, ?)", @@ -69,6 +100,16 @@ func (d* Database) CreateEntry(projectName, description string) (*TimeEntry, err return d.GetEntry(id) } +// GetRunningEntry retrieves the most recently started time entry that is still running +// (i.e. has a NULL end_time) from the time_entries table. The query orders by +// start_time descending and returns at most one row. +// +// If there is no running entry, GetRunningEntry returns (nil, nil). If the database +// query or scan fails, it returns a non-nil error describing the failure. +// +// The function scans id, project_name, start_time, end_time and description into a +// TimeEntry. The EndTime field on the returned TimeEntry is set only if the scanned +// end_time is non-NULL (sql.NullTime.Valid). func (d* Database) GetRunningEntry() (*TimeEntry, error) { var entry TimeEntry var endTime sql.NullTime @@ -96,6 +137,11 @@ func (d* Database) GetRunningEntry() (*TimeEntry, error) { return &entry, nil } +// StopEntry sets the end_time of the time entry identified by id to the current time. +// It updates the corresponding row in the time_entries table using time.Now(). +// If the update fails (for example if the row does not exist or the database returns an error), +// an error is returned wrapped with context. This method overwrites any existing end_time value +// and does not return the updated entry or perform additional validation on id. func (d* Database) StopEntry(id int64) error { _, err := d.db.Exec( "UPDATE time_entries SET end_time = ? WHERE id = ?", @@ -110,6 +156,13 @@ func (d* Database) StopEntry(id int64) error { return nil } +// GetEntry retrieves a TimeEntry by its ID from the database. +// It queries the time_entries table for id, project_name, start_time, end_time and description, +// scans the result into a TimeEntry value, and returns a pointer to it. +// If the end_time column is NULL in the database, the returned TimeEntry.EndTime will be nil; +// otherwise EndTime will point to the retrieved time value. +// If no row is found or an error occurs during query/scan, an error is returned (wrapped with +// the context "failed to get entry"). func (d* Database) GetEntry(id int64) (*TimeEntry, error) { var entry TimeEntry var endTime sql.NullTime @@ -131,6 +184,9 @@ func (d* Database) GetEntry(id int64) (*TimeEntry, error) { return &entry, nil } +// Close closes the Database, releasing any underlying resources. +// It delegates to the wrapped database's Close method and returns any error encountered. +// After Close is called, the Database must not be used for further operations. func (d* Database) Close() error { return d.db.Close() } \ No newline at end of file From 5eee3fd33115c8eb6561181bd75d16c3c2cef470 Mon Sep 17 00:00:00 2001 From: Dylan Ravel Date: Sun, 7 Dec 2025 09:46:12 -0800 Subject: [PATCH 3/8] Implement stop command for time tracking Added logic to stop the currently running time tracking session, including error handling and duration formatting. The command now interacts with the storage layer to find and stop active sessions, and prints the tracked duration in a human-readable format. --- cmd/stop.go | 75 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/cmd/stop.go b/cmd/stop.go index e18220e..938dcb4 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -2,35 +2,74 @@ package cmd import ( "fmt" + "os" + "time" + "github.com/DylanDevelops/tmpo/internal/storage" "github.com/spf13/cobra" ) // stopCmd represents the stop command var stopCmd = &cobra.Command{ Use: "stop", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("stop called") + Short: "Stop tracking time", + Long: `Stop the currently running time tracking session.`, + Run: func(cmd* cobra.Command, args []string) { + db, err := storage.Initialize() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + + os.Exit(1) + } + + defer db.Close() + + running, err := db.GetRunningEntry() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + + os.Exit(1) + } + + if running == nil { + fmt.Println("No active time tracking session.") + + os.Exit(0) + } + + err = db.StopEntry(running.ID) + if(err != nil) { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + + os.Exit(1) + } + + duration := time.Since(running.StartTime) + + fmt.Printf("[tmpo] Stopped tracking '%s'\n", running.ProjectName) + fmt.Printf(" Duration: %s\n", formatDuration(duration)) }, } -func init() { - rootCmd.AddCommand(stopCmd) +// formatDuration formats d into a concise, human-readable string using hours, minutes and seconds. +// It returns "h m s" when the duration is at least one hour, "m s" when the duration +// is at least one minute but less than an hour, and "s" for durations under one minute. +// Hours, minutes and seconds are derived from d using integer truncation (no fractional parts). +// This function is intended for non-negative durations; behavior for negative durations is unspecified. +func formatDuration(d time.Duration) string { + hours := int(d.Hours()) + minutes := int(d.Minutes()) % 60 + seconds := int(d.Seconds()) % 60 - // Here you will define your flags and configuration settings. + if hours > 0 { + return fmt.Sprintf("%dh %dm %ds", hours, minutes, seconds) + } else if minutes > 0 { + return fmt.Sprintf("%dm %ds", minutes, seconds) + } - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // stopCmd.PersistentFlags().String("foo", "", "A help for foo") + return fmt.Sprintf("%ds", seconds) +} - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // stopCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +func init() { + rootCmd.AddCommand(stopCmd) } From 7b1727d25c8738c8bc777ef15c347393e9431256 Mon Sep 17 00:00:00 2001 From: Dylan Ravel Date: Sun, 7 Dec 2025 10:01:26 -0800 Subject: [PATCH 4/8] Implement status command to show tracking session Added logic to the status command to display information about the currently running time tracking session, including project name, start time, duration, and description. Also made minor formatting adjustments in start and stop commands for consistency. --- cmd/start.go | 2 +- cmd/status.go | 59 ++++++++++++++++++++++++++++++++++----------------- cmd/stop.go | 2 +- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/cmd/start.go b/cmd/start.go index 6da043d..4ccd46a 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -12,7 +12,7 @@ import ( var startCmd = &cobra.Command{ Use: "start [description]", Short: "Start tracking time", - Long: `Start a new time tracking session for the current project.`, + Long: `Start a new time tracking session for the current project.`, Run: func(cmd* cobra.Command, args []string) { db, err := storage.Initialize() if err != nil { diff --git a/cmd/status.go b/cmd/status.go index 5e294de..1bf28e3 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -2,35 +2,54 @@ package cmd import ( "fmt" + "os" + "time" + "github.com/DylanDevelops/tmpo/internal/storage" "github.com/spf13/cobra" ) -// statusCmd represents the status command var statusCmd = &cobra.Command{ Use: "status", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("status called") + Short: "Show current tracking status", + Long: `Display information about the currently running time tracking session.`, + + Run: func(cmd* cobra.Command, args []string) { + db, err := storage.Initialize() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + + os.Exit(1) + } + + defer db.Close() + + running, err := db.GetRunningEntry() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + + os.Exit(1) + } + + if running == nil { + fmt.Println("[tmpo] Not currently tracking time") + fmt.Println("\nUse 'tmpo start' to begin tracking") + + return + } + + duration := time.Since(running.StartTime) + + fmt.Printf("[tmpo] Currently tracking: %s\n", running.ProjectName) + fmt.Printf(" Started: %s\n", running.StartTime.Format("3:04 PM")) + fmt.Printf(" Duration: %s\n", formatDuration(duration)) + + if running.Description != "" { + fmt.Printf(" Description: %s\n", running.Description) + } }, } func init() { rootCmd.AddCommand(statusCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // statusCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // statusCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/cmd/stop.go b/cmd/stop.go index 938dcb4..527f373 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -13,7 +13,7 @@ import ( var stopCmd = &cobra.Command{ Use: "stop", Short: "Stop tracking time", - Long: `Stop the currently running time tracking session.`, + Long: `Stop the currently running time tracking session.`, Run: func(cmd* cobra.Command, args []string) { db, err := storage.Initialize() if err != nil { From f644f6c6e3678cc06be0f661f1b1a1505355ab0e Mon Sep 17 00:00:00 2001 From: Dylan Ravel Date: Sun, 7 Dec 2025 10:03:34 -0800 Subject: [PATCH 5/8] Remove root command implementation Deleted the internal/commands/root.go file. This may be part of a refactor or cleanup to remove unused or obsolete command logic. --- internal/commands/root.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 internal/commands/root.go diff --git a/internal/commands/root.go b/internal/commands/root.go deleted file mode 100644 index e69de29..0000000 From a53ca4fa65c01cefac259763f3275e0c6c57f32c Mon Sep 17 00:00:00 2001 From: Dylan Ravel Date: Sun, 7 Dec 2025 10:10:18 -0800 Subject: [PATCH 6/8] Add sqlite3 driver import to db.go Imported the github.com/mattn/go-sqlite3 package to enable SQLite database support in the storage layer. --- internal/storage/db.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/storage/db.go b/internal/storage/db.go index 5264863..a04eaab 100644 --- a/internal/storage/db.go +++ b/internal/storage/db.go @@ -6,6 +6,8 @@ import ( "os" "path/filepath" "time" + + _ "github.com/mattn/go-sqlite3" ) // Database is a thin wrapper around an *sql.DB connection pool. From c24607c352a6bedbd55195a2f7821c903ff6524a Mon Sep 17 00:00:00 2001 From: Dylan Ravel Date: Sun, 7 Dec 2025 10:16:22 -0800 Subject: [PATCH 7/8] Update version, clean dependencies, and improve errors Bump CLI version to 0.1.0. Remove unused dependencies from go.mod and go.sum. Improve error message consistency in internal/storage/db.go by using lowercase. Remove license comment from main.go. --- cmd/root.go | 2 +- go.mod | 19 +++++-------------- go.sum | 23 ----------------------- internal/storage/db.go | 6 +++--- main.go | 4 ---- 5 files changed, 9 insertions(+), 45 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 239c539..241df2e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,5 +23,5 @@ func Execute() { } func init() { - rootCmd.Version = "0.0.1" + rootCmd.Version = "0.1.0" } diff --git a/go.mod b/go.mod index 3718c08..f06c362 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,11 @@ module github.com/DylanDevelops/tmpo go 1.25.5 require ( - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/mattn/go-sqlite3 v1.14.32 + github.com/spf13/cobra v1.10.2 +) + +require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/mattn/go-sqlite3 v1.14.32 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/sagikazarmark/locafero v0.12.0 // indirect - github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect - github.com/spf13/afero v1.15.0 // indirect - github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/spf13/viper v1.21.0 // indirect - github.com/subosito/gotenv v1.6.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect ) diff --git a/go.sum b/go.sum index 74a60cb..abc4a22 100644 --- a/go.sum +++ b/go.sum @@ -1,36 +1,13 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= -github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= -github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= -github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= -github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= -github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/storage/db.go b/internal/storage/db.go index a04eaab..d295b8a 100644 --- a/internal/storage/db.go +++ b/internal/storage/db.go @@ -90,13 +90,13 @@ func (d* Database) CreateEntry(projectName, description string) (*TimeEntry, err ) if err != nil { - return nil, fmt.Errorf("Failed to create entry: %w", err) + return nil, fmt.Errorf("failed to create entry: %w", err) } id, err := result.LastInsertId() if err != nil { - return nil, fmt.Errorf("Failed to get last insert id: %w", err) + return nil, fmt.Errorf("failed to get last insert id: %w", err) } return d.GetEntry(id) @@ -129,7 +129,7 @@ func (d* Database) GetRunningEntry() (*TimeEntry, error) { } if err != nil { - return nil, fmt.Errorf("Failed to get running entry: %w", err) + return nil, fmt.Errorf("failed to get running entry: %w", err) } if endTime.Valid { diff --git a/main.go b/main.go index 358c988..4eb2443 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,3 @@ -/* -Copyright © 2025 NAME HERE - -*/ package main import "github.com/DylanDevelops/tmpo/cmd" From 27cc61a6268d87b009710ea70c356d4980de0360 Mon Sep 17 00:00:00 2001 From: Dylan Ravel Date: Sun, 7 Dec 2025 10:19:37 -0800 Subject: [PATCH 8/8] Update stop.go --- cmd/stop.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/stop.go b/cmd/stop.go index 527f373..7941d44 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -47,7 +47,7 @@ var stopCmd = &cobra.Command{ duration := time.Since(running.StartTime) fmt.Printf("[tmpo] Stopped tracking '%s'\n", running.ProjectName) - fmt.Printf(" Duration: %s\n", formatDuration(duration)) + fmt.Printf(" Total Duration: %s\n", formatDuration(duration)) }, }