summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-04 10:50:07 +0200
committerPaul Buetow <paul@buetow.org>2026-03-04 10:50:07 +0200
commit97aa8a6f666f5f40859c8a9aa4948bde435cf18f (patch)
tree0cb5928cd6a1220607dbf64e234a2522acac2848
parentc25c9002f3214e07b041aefa26d5d13c26757839 (diff)
Rename project to timesamurai and release v0.5.0v0.5.0
-rw-r--r--.gitignore4
-rw-r--r--Magefile.go6
-rw-r--r--README.md70
-rw-r--r--cmd/timesamurai/main.go (renamed from cmd/timr/main.go)2
-rw-r--r--go.mod2
-rwxr-xr-xinstall-fish-integration.fish42
-rw-r--r--internal/cli/root.go59
-rw-r--r--internal/cli/root_test.go122
-rw-r--r--internal/cli/timer.go28
-rw-r--r--internal/cli/timer_test.go2
-rw-r--r--internal/cli/tui.go11
-rw-r--r--internal/cli/tui_test.go5
-rw-r--r--internal/cli/work.go46
-rw-r--r--internal/cli/work_test.go44
-rw-r--r--internal/config/blackbox_test.go2
-rw-r--r--internal/config/config.go2
-rw-r--r--internal/config/doc.go2
-rw-r--r--internal/duration/blackbox_test.go2
-rw-r--r--internal/duration/doc.go2
-rw-r--r--internal/timefmt/doc.go2
-rw-r--r--internal/timer/timer.go4
-rw-r--r--internal/tui/doc.go2
-rw-r--r--internal/tui/entries.go638
-rw-r--r--internal/tui/entries_test.go197
-rw-r--r--internal/tui/report.go11
-rw-r--r--internal/tui/report_test.go13
-rw-r--r--internal/tui/styles.go69
-rw-r--r--internal/tui/theme.go103
-rw-r--r--internal/tui/timer.go16
-rw-r--r--internal/tui/timer_test.go4
-rw-r--r--internal/tui/tui.go191
-rw-r--r--internal/tui/tui_test.go123
-rw-r--r--internal/version.go2
-rw-r--r--internal/worktime/blackbox_test.go2
-rw-r--r--internal/worktime/comprehensive_test.go2
-rw-r--r--internal/worktime/entries.go21
-rw-r--r--internal/worktime/entries_test.go22
-rw-r--r--internal/worktime/import.go2
-rw-r--r--internal/worktime/integrity.go144
-rw-r--r--internal/worktime/integrity_test.go77
-rw-r--r--internal/worktime/report.go2
-rw-r--r--internal/worktime/report_test.go2
42 files changed, 1854 insertions, 248 deletions
diff --git a/.gitignore b/.gitignore
index 2007bca..8322a36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
-/timr
+/timesamurai
/main
-/cmd/timr/timr
+/cmd/timesamurai/timesamurai
diff --git a/Magefile.go b/Magefile.go
index 4b150c0..3b00b47 100644
--- a/Magefile.go
+++ b/Magefile.go
@@ -9,11 +9,11 @@ import (
var Default = Build
func Build() error {
- return sh.RunV("go", "build", "-o", "timr", "./cmd/timr")
+ return sh.RunV("go", "build", "-o", "timesamurai", "./cmd/timesamurai")
}
func Run() error {
- return sh.RunV("go", "run", "./cmd/timr")
+ return sh.RunV("go", "run", "./cmd/timesamurai")
}
func Test() error {
@@ -25,5 +25,5 @@ func Lint() error {
}
func Install() error {
- return sh.RunV("go", "install", "./cmd/timr")
+ return sh.RunV("go", "install", "./cmd/timesamurai")
}
diff --git a/README.md b/README.md
index ebd97bf..aa095bd 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
-# timr
+# timesamurai
-`timr` is a terminal time-tracking tool that combines:
+`timesamurai` is a terminal time-tracking tool that combines:
- a stopwatch timer,
- worktime-style work log tracking,
- weekly reporting,
- and a Bubble Tea TUI.
-Current version: `v0.4.0`.
+Current version: `v0.5.0`.
## Installation
@@ -26,49 +26,50 @@ mage build
Or build directly:
```bash
-go build -o timr ./cmd/timr
+go build -o timesamurai ./cmd/timesamurai
```
## CLI Overview
-`timr` now uses Cobra and supports global flags:
+`timesamurai` now uses Cobra and supports global flags:
```bash
-timr --version
-timr --config /path/to/config.json
+timesamurai --version
+timesamurai --config /path/to/config.json
```
Top-level command groups:
-- `timr timer ...`
-- `timr work ...`
-- `timr tui`
+- `timesamurai timer ...`
+- `timesamurai work ...`
+- `timesamurai tui`
### `timer` Commands
```bash
-timr timer start
-timr timer stop
-timr timer continue
-timr timer reset
-timr timer status [--raw|--raw-minutes]
-timr timer prompt
-timr timer track <description>
-timr timer live [-f|--font <font>]
+timesamurai timer start
+timesamurai timer stop
+timesamurai timer continue
+timesamurai timer reset
+timesamurai timer status [--raw|--raw-minutes]
+timesamurai timer prompt
+timesamurai timer track <description>
+timesamurai timer live [-f|--font <font>]
```
### `work` Commands
```bash
-timr work login [-c|--category <cat>] [--at <time>] [-d|--descr <text>] [--start-timer]
-timr work logout [-c|--category <cat>] [--at <time>] [-d|--descr <text>] [--stop-timer]
-timr work add <duration> [-c|--category <cat>] [--at <time>] [-d|--descr <text>]
-timr work sub <duration> [-c|--category <cat>] [--at <time>] [-d|--descr <text>]
-timr work use-buffer <duration> [--at <time>] [-d|--descr <text>]
-timr work status
-timr work report [--verbose] [--no-color]
-timr work edit
-timr work import <file>
+timesamurai work login [-c|--category <cat>] [--at <time>] [-d|--descr <text>] [--start-timer]
+timesamurai work logout [-c|--category <cat>] [--at <time>] [-d|--descr <text>] [--stop-timer]
+timesamurai work add <duration> [-c|--category <cat>] [--at <time>] [-d|--descr <text>]
+timesamurai work sub <duration> [-c|--category <cat>] [--at <time>] [-d|--descr <text>]
+timesamurai work day-off [--at <time>] [-d|--descr <text>]
+timesamurai work use-buffer <duration> [--at <time>] [-d|--descr <text>]
+timesamurai work status
+timesamurai work report [--verbose] [--no-color]
+timesamurai work edit
+timesamurai work import <file>
```
## TUI
@@ -76,20 +77,23 @@ timr work import <file>
Launch the TUI:
```bash
-timr tui
+timesamurai tui
+timesamurai tui --disco
```
-The scaffold currently includes:
+The TUI includes:
- tab-based navigation (`Entries`, `Report`, `Timer`),
-- vi-style global keys (`Tab`, `gt`, `gT`, `1/2/3`, `?`, `q`, `ZQ`),
-- entries browsing/editing flows,
+- TaskSamurai-inspired table styling and status bars,
+- vi-style global keys (`Tab`, `gt`, `gT`, `1/2/3`, `?`/`H`, `q`, `ZQ`),
+- theme controls (`c` randomize theme, `C` reset, `x` toggle disco mode),
+- entries timeline table flows (navigate columns with `h/l` and press `Enter` to edit selected field, including date/time/value/description; `D` opens day-off datepicker),
- report browsing,
- timer screen with work login/logout toggle (`l`).
## TUI Screenshots
-Screenshots section (`v0.4.0` baseline):
+Screenshots section (`v0.5.0` baseline):
- Entries screen: _to be captured_
- Report screen: _to be captured_
@@ -103,4 +107,4 @@ Install fish prompt helper:
./install-fish-integration.fish
```
-Then add `timr_prompt` to your `fish_prompt` or `fish_right_prompt`.
+Then add `timesamurai_prompt` to your `fish_prompt` or `fish_right_prompt`.
diff --git a/cmd/timr/main.go b/cmd/timesamurai/main.go
index eea7d78..f1a8ab8 100644
--- a/cmd/timr/main.go
+++ b/cmd/timesamurai/main.go
@@ -4,7 +4,7 @@ import (
"fmt"
"os"
- "codeberg.org/snonux/timr/internal/cli"
+ "codeberg.org/snonux/timesamurai/internal/cli"
)
func main() {
diff --git a/go.mod b/go.mod
index ad9840b..e572615 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module codeberg.org/snonux/timr
+module codeberg.org/snonux/timesamurai
go 1.24.3
diff --git a/install-fish-integration.fish b/install-fish-integration.fish
index b90be24..90024d8 100755
--- a/install-fish-integration.fish
+++ b/install-fish-integration.fish
@@ -2,9 +2,9 @@
set -l fish_config_dir (set -q XDG_CONFIG_HOME; and echo "$XDG_CONFIG_HOME/fish"; or echo "$HOME/.config/fish")
set -l fish_conf_d "$fish_config_dir/conf.d"
-set -l integration_file "$fish_conf_d/timr.fish"
+set -l integration_file "$fish_conf_d/timesamurai.fish"
-echo "Installing timr fish shell integration..."
+echo "Installing timesamurai fish shell integration..."
# Create fish config directory if it doesn't exist
if not test -d "$fish_conf_d"
@@ -14,12 +14,12 @@ end
# Write the integration file
echo "\
-function timr_prompt -d \"Display timr timr_status in the prompt\"
- if command -v timr >/dev/null
- set -l timr_status (timr prompt)
- if test -n \"\$timr_status\"
- set -l icon (string sub -l 1 -- \"\$timr_status\")
- set -l time (string sub -s 2 -- \"\$timr_status\")
+function timesamurai_prompt -d \"Display timesamurai timesamurai_status in the prompt\"
+ if command -v timesamurai >/dev/null
+ set -l timesamurai_status (timesamurai prompt)
+ if test -n \"\$timesamurai_status\"
+ set -l icon (string sub -l 1 -- \"\$timesamurai_status\")
+ set -l time (string sub -s 2 -- \"\$timesamurai_status\")
if test \"\$icon\" = \"▶\"
set_color green
else
@@ -32,30 +32,30 @@ function timr_prompt -d \"Display timr timr_status in the prompt\"
end
end
-complete -c timr -n __fish_use_subcommand -a start -d \"Start the timer\"
-complete -c timr -n __fish_use_subcommand -a stop -d \"Stop the timer\"
-complete -c timr -n __fish_use_subcommand -a pause -d \"Pause the timer\"
-complete -c timr -n __fish_use_subcommand -a continue -d \"Continue the timer\"
-complete -c timr -n __fish_use_subcommand -a cont -d \"Continue the timer\"
-complete -c timr -n __fish_use_subcommand -a status -d \"Show the timer status\"
-complete -c timr -n __fish_use_subcommand -a reset -d \"Reset the timer\"
-complete -c timr -n __fish_use_subcommand -a track -d \"Save time with description\"
-complete -c timr -n __fish_use_subcommand -a live -d \"Show the live timer\"
-complete -c timr -n __fish_use_subcommand -a prompt -d \"Show the prompt status\"
+complete -c timesamurai -n __fish_use_subcommand -a start -d \"Start the timer\"
+complete -c timesamurai -n __fish_use_subcommand -a stop -d \"Stop the timer\"
+complete -c timesamurai -n __fish_use_subcommand -a pause -d \"Pause the timer\"
+complete -c timesamurai -n __fish_use_subcommand -a continue -d \"Continue the timer\"
+complete -c timesamurai -n __fish_use_subcommand -a cont -d \"Continue the timer\"
+complete -c timesamurai -n __fish_use_subcommand -a status -d \"Show the timer status\"
+complete -c timesamurai -n __fish_use_subcommand -a reset -d \"Reset the timer\"
+complete -c timesamurai -n __fish_use_subcommand -a track -d \"Save time with description\"
+complete -c timesamurai -n __fish_use_subcommand -a live -d \"Show the live timer\"
+complete -c timesamurai -n __fish_use_subcommand -a prompt -d \"Show the prompt status\"
# Font completions for live mode
-complete -c timr -n \"__fish_seen_subcommand_from live\" -s f -l font -d \"Font style\" -a \"doom mono12 rebel ansi ansiShadow\"
+complete -c timesamurai -n \"__fish_seen_subcommand_from live\" -s f -l font -d \"Font style\" -a \"doom mono12 rebel ansi ansiShadow\"
" > "$integration_file"
echo "Created: $integration_file"
echo ""
echo "Installation complete!"
echo ""
-echo "To display timr in your prompt, add this to your fish_prompt or fish_right_prompt:"
+echo "To display timesamurai in your prompt, add this to your fish_prompt or fish_right_prompt:"
echo ""
echo " function fish_prompt"
echo " # ... your existing prompt ..."
-echo " printf '%s ' (timr_prompt)"
+echo " printf '%s ' (timesamurai_prompt)"
echo " end"
echo ""
echo "Restart your fish shell or run 'source $integration_file' to apply changes."
diff --git a/internal/cli/root.go b/internal/cli/root.go
index faba592..8b5ad09 100644
--- a/internal/cli/root.go
+++ b/internal/cli/root.go
@@ -2,10 +2,14 @@ package cli
import (
"context"
+ "errors"
"fmt"
+ "strings"
+ "time"
- timr "codeberg.org/snonux/timr/internal"
- "codeberg.org/snonux/timr/internal/config"
+ timesamurai "codeberg.org/snonux/timesamurai/internal"
+ "codeberg.org/snonux/timesamurai/internal/config"
+ "codeberg.org/snonux/timesamurai/internal/worktime"
"github.com/spf13/cobra"
)
@@ -20,9 +24,10 @@ func Execute() error {
func NewRootCmd() *cobra.Command {
var configPath string
var showVersion bool
+ var checkDBIntegrity bool
cmd := &cobra.Command{
- Use: "timr",
+ Use: "timesamurai",
Short: "Track time from your terminal",
SilenceUsage: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
@@ -40,15 +45,19 @@ func NewRootCmd() *cobra.Command {
},
RunE: func(cmd *cobra.Command, args []string) error {
if showVersion {
- _, err := fmt.Fprintln(cmd.OutOrStdout(), timr.Version)
+ _, err := fmt.Fprintln(cmd.OutOrStdout(), timesamurai.Version)
return err
}
+ if checkDBIntegrity {
+ return runDBIntegrityCheck(cmd)
+ }
return cmd.Help()
},
}
cmd.Flags().BoolVar(&showVersion, "version", false, "Print version and exit")
+ cmd.Flags().BoolVar(&checkDBIntegrity, "check-db-integrity", false, "Validate worktime database integrity and exit")
cmd.PersistentFlags().StringVar(&configPath, "config", "", "Path to config file")
cmd.AddCommand(newTimerCmd())
cmd.AddCommand(newWorkCmd())
@@ -81,3 +90,45 @@ func currentConfig(cmd *cobra.Command) config.Config {
}
return cfg
}
+
+func runDBIntegrityCheck(cmd *cobra.Command) error {
+ cfg := currentConfig(cmd)
+ entries, err := worktime.LoadAll(cfg.WorktimeDBDir)
+ if err != nil {
+ return err
+ }
+
+ issues := worktime.CheckEntriesIntegrity(entries, worktime.DefaultMaxSessionSpan)
+ openSessions := worktime.OpenSessions(entries)
+
+ lines := make([]string, 0, len(issues)+len(openSessions)+2)
+ if len(issues) == 0 {
+ lines = append(lines, "Database integrity check passed.")
+ } else {
+ lines = append(lines, fmt.Sprintf("Database integrity check found %d issue(s):", len(issues)))
+ for idx, issue := range issues {
+ lines = append(lines, fmt.Sprintf("%d. %s", idx+1, issue.String()))
+ }
+ }
+
+ if len(openSessions) > 0 {
+ lines = append(lines, fmt.Sprintf("Warning: currently logged in (%d open session(s)):", len(openSessions)))
+ for _, session := range openSessions {
+ lines = append(lines, fmt.Sprintf(
+ "- category=%s source=%s since=%s",
+ session.Category,
+ session.Login.Source,
+ time.Unix(session.Login.Epoch, 0).Format("2006-01-02 15:04:05"),
+ ))
+ }
+ }
+
+ _, printErr := fmt.Fprintln(cmd.OutOrStdout(), strings.Join(lines, "\n"))
+ if printErr != nil {
+ return printErr
+ }
+ if len(issues) > 0 {
+ return errors.New("database integrity check failed")
+ }
+ return nil
+}
diff --git a/internal/cli/root_test.go b/internal/cli/root_test.go
index 2b12514..e1f74b1 100644
--- a/internal/cli/root_test.go
+++ b/internal/cli/root_test.go
@@ -7,8 +7,10 @@ import (
"path/filepath"
"strings"
"testing"
+ "time"
- timr "codeberg.org/snonux/timr/internal"
+ timesamurai "codeberg.org/snonux/timesamurai/internal"
+ "codeberg.org/snonux/timesamurai/internal/worktime"
)
func TestRootVersionFlag(t *testing.T) {
@@ -22,8 +24,8 @@ func TestRootVersionFlag(t *testing.T) {
t.Fatalf("Execute() error = %v", err)
}
- if strings.TrimSpace(out.String()) != timr.Version {
- t.Fatalf("output = %q, want %q", strings.TrimSpace(out.String()), timr.Version)
+ if strings.TrimSpace(out.String()) != timesamurai.Version {
+ t.Fatalf("output = %q, want %q", strings.TrimSpace(out.String()), timesamurai.Version)
}
}
@@ -96,8 +98,8 @@ func TestVersionSkipsConfigLoading(t *testing.T) {
t.Fatalf("Execute() error = %v", err)
}
- if strings.TrimSpace(out.String()) != timr.Version {
- t.Fatalf("output = %q, want %q", strings.TrimSpace(out.String()), timr.Version)
+ if strings.TrimSpace(out.String()) != timesamurai.Version {
+ t.Fatalf("output = %q, want %q", strings.TrimSpace(out.String()), timesamurai.Version)
}
}
@@ -124,3 +126,113 @@ func TestRootUsesDefaultConfigWhenNoFileExists(t *testing.T) {
t.Fatalf("WorktimeDBDir = %q, want %q", cfg.WorktimeDBDir, wantDir)
}
}
+
+func TestRootCheckDBIntegrityPasses(t *testing.T) {
+ dbDir := t.TempDir()
+ host := "host-a"
+
+ if _, err := worktime.Login(dbDir, host, "work", time.Unix(100, 0), ""); err != nil {
+ t.Fatalf("Login() error = %v", err)
+ }
+ if _, err := worktime.Logout(dbDir, host, "work", time.Unix(200, 0), ""); err != nil {
+ t.Fatalf("Logout() error = %v", err)
+ }
+
+ cfgPath := writeRootConfig(t, dbDir, host)
+
+ var out bytes.Buffer
+ cmd := NewRootCmd()
+ cmd.SetOut(&out)
+ cmd.SetErr(&out)
+ cmd.SetArgs([]string{"--config", cfgPath, "--check-db-integrity"})
+
+ if err := cmd.Execute(); err != nil {
+ t.Fatalf("Execute() error = %v (output: %q)", err, out.String())
+ }
+ if !strings.Contains(out.String(), "Database integrity check passed.") {
+ t.Fatalf("unexpected output: %q", out.String())
+ }
+}
+
+func TestRootCheckDBIntegrityFailsOnIssues(t *testing.T) {
+ dbDir := t.TempDir()
+ host := "host-a"
+
+ db := worktime.Database{
+ Entries: map[string][]worktime.Entry{
+ host: {
+ {
+ Action: "logout",
+ What: "work",
+ Epoch: 100,
+ Source: host,
+ Human: time.Unix(100, 0).Format("Mon 02.01.2006 15:04:05"),
+ },
+ },
+ },
+ }
+ if err := worktime.SaveHost(dbDir, host, db); err != nil {
+ t.Fatalf("SaveHost() error = %v", err)
+ }
+
+ cfgPath := writeRootConfig(t, dbDir, host)
+
+ var out bytes.Buffer
+ cmd := NewRootCmd()
+ cmd.SetOut(&out)
+ cmd.SetErr(&out)
+ cmd.SetArgs([]string{"--config", cfgPath, "--check-db-integrity"})
+
+ err := cmd.Execute()
+ if err == nil {
+ t.Fatal("Execute() error = nil, want integrity failure")
+ }
+ if !strings.Contains(err.Error(), "database integrity check failed") {
+ t.Fatalf("Execute() error = %v, want integrity failure", err)
+ }
+ if !strings.Contains(out.String(), "Database integrity check found") {
+ t.Fatalf("unexpected output: %q", out.String())
+ }
+}
+
+func TestRootCheckDBIntegrityWarnsForOpenSession(t *testing.T) {
+ dbDir := t.TempDir()
+ host := "host-a"
+
+ if _, err := worktime.Login(dbDir, host, "work", time.Unix(100, 0), ""); err != nil {
+ t.Fatalf("Login() error = %v", err)
+ }
+
+ cfgPath := writeRootConfig(t, dbDir, host)
+
+ var out bytes.Buffer
+ cmd := NewRootCmd()
+ cmd.SetOut(&out)
+ cmd.SetErr(&out)
+ cmd.SetArgs([]string{"--config", cfgPath, "--check-db-integrity"})
+
+ if err := cmd.Execute(); err != nil {
+ t.Fatalf("Execute() error = %v (output: %q)", err, out.String())
+ }
+ if !strings.Contains(out.String(), "Database integrity check passed.") {
+ t.Fatalf("unexpected output: %q", out.String())
+ }
+ if !strings.Contains(out.String(), "Warning: currently logged in") {
+ t.Fatalf("expected open-session warning in output: %q", out.String())
+ }
+}
+
+func writeRootConfig(t *testing.T, dbDir, host string) string {
+ t.Helper()
+
+ content := `{
+ "worktime_db_dir": "` + dbDir + `",
+ "hostname": "` + host + `"
+}
+`
+ path := filepath.Join(t.TempDir(), "config.json")
+ if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
+ t.Fatalf("write config file: %v", err)
+ }
+ return path
+}
diff --git a/internal/cli/timer.go b/internal/cli/timer.go
index 37e50d0..e9889c4 100644
--- a/internal/cli/timer.go
+++ b/internal/cli/timer.go
@@ -8,10 +8,10 @@ import (
"strings"
"time"
- "codeberg.org/snonux/timr/internal/ascii"
- timrTimer "codeberg.org/snonux/timr/internal/timer"
- tuiapp "codeberg.org/snonux/timr/internal/tui"
- "codeberg.org/snonux/timr/internal/worktime"
+ "codeberg.org/snonux/timesamurai/internal/ascii"
+ timesamuraiTimer "codeberg.org/snonux/timesamurai/internal/timer"
+ tuiapp "codeberg.org/snonux/timesamurai/internal/tui"
+ "codeberg.org/snonux/timesamurai/internal/worktime"
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
)
@@ -44,7 +44,7 @@ func newTimerStartCmd() *cobra.Command {
return err
}
- output, err := timrTimer.StartTimer(hasElapsed)
+ output, err := timesamuraiTimer.StartTimer(hasElapsed)
if err != nil {
return err
}
@@ -61,7 +61,7 @@ func newTimerStopCmd() *cobra.Command {
Use: "stop",
Short: "Stop the timer",
RunE: func(cmd *cobra.Command, args []string) error {
- output, err := timrTimer.StopTimer()
+ output, err := timesamuraiTimer.StopTimer()
if err != nil {
return fmt.Errorf("stop timer: %w", err)
}
@@ -85,7 +85,7 @@ func newTimerContinueCmd() *cobra.Command {
output := "Timer is at 0, cannot continue."
if hasElapsed {
- output, err = timrTimer.StartTimer(true)
+ output, err = timesamuraiTimer.StartTimer(true)
if err != nil {
return err
}
@@ -101,7 +101,7 @@ func newTimerResetCmd() *cobra.Command {
Use: "reset",
Short: "Reset the timer",
RunE: func(cmd *cobra.Command, args []string) error {
- output, err := timrTimer.ResetTimer()
+ output, err := timesamuraiTimer.ResetTimer()
if err != nil {
return fmt.Errorf("reset timer: %w", err)
}
@@ -129,11 +129,11 @@ func newTimerStatusCmd() *cobra.Command {
switch {
case raw:
- output, err = timrTimer.GetRawStatus()
+ output, err = timesamuraiTimer.GetRawStatus()
case rawMinutes:
- output, err = timrTimer.GetRawMinutesStatus()
+ output, err = timesamuraiTimer.GetRawMinutesStatus()
default:
- output, err = timrTimer.GetStatus()
+ output, err = timesamuraiTimer.GetStatus()
}
if err != nil {
return err
@@ -153,7 +153,7 @@ func newTimerPromptCmd() *cobra.Command {
Use: "prompt",
Short: "Show prompt-friendly timer status",
RunE: func(cmd *cobra.Command, args []string) error {
- output, err := timrTimer.GetPromptStatus()
+ output, err := timesamuraiTimer.GetPromptStatus()
if err != nil {
return fmt.Errorf("get prompt timer status: %w", err)
}
@@ -169,7 +169,7 @@ func newTimerTrackCmd() *cobra.Command {
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
description := strings.Join(args, " ")
- output, err := timrTimer.TrackTime(description)
+ output, err := timesamuraiTimer.TrackTime(description)
if err != nil {
return fmt.Errorf("track timer entry %q: %w", description, err)
}
@@ -242,7 +242,7 @@ func syncWorktimeWithTimer(cmd *cobra.Command, start bool) error {
}
func timerHasElapsed() (bool, error) {
- rawStatus, err := timrTimer.GetRawStatus()
+ rawStatus, err := timesamuraiTimer.GetRawStatus()
if err != nil {
return false, fmt.Errorf("get raw timer status: %w", err)
}
diff --git a/internal/cli/timer_test.go b/internal/cli/timer_test.go
index 572d5a0..be4391e 100644
--- a/internal/cli/timer_test.go
+++ b/internal/cli/timer_test.go
@@ -6,7 +6,7 @@ import (
"testing"
"time"
- "codeberg.org/snonux/timr/internal/worktime"
+ "codeberg.org/snonux/timesamurai/internal/worktime"
)
func TestTimerStartAndStopCommands(t *testing.T) {
diff --git a/internal/cli/tui.go b/internal/cli/tui.go
index ff4f2aa..474ffc2 100644
--- a/internal/cli/tui.go
+++ b/internal/cli/tui.go
@@ -1,17 +1,19 @@
package cli
import (
- tuiapp "codeberg.org/snonux/timr/internal/tui"
+ tuiapp "codeberg.org/snonux/timesamurai/internal/tui"
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
)
func newTUICmd() *cobra.Command {
- return &cobra.Command{
+ var disco bool
+
+ cmd := &cobra.Command{
Use: "tui",
Short: "Launch full-screen TUI",
RunE: func(cmd *cobra.Command, args []string) error {
- model, err := tuiapp.NewModelWithConfig(currentConfig(cmd))
+ model, err := tuiapp.NewModelWithConfigAndDisco(currentConfig(cmd), disco)
if err != nil {
return err
}
@@ -19,4 +21,7 @@ func newTUICmd() *cobra.Command {
return program.Start()
},
}
+
+ cmd.Flags().BoolVar(&disco, "disco", false, "Enable disco mode (random theme changes)")
+ return cmd
}
diff --git a/internal/cli/tui_test.go b/internal/cli/tui_test.go
index 9d8f481..8ec4afd 100644
--- a/internal/cli/tui_test.go
+++ b/internal/cli/tui_test.go
@@ -25,4 +25,9 @@ func TestNewTUICmdMetadata(t *testing.T) {
if cmd.Short == "" {
t.Fatal("Short description should not be empty")
}
+
+ flag := cmd.Flags().Lookup("disco")
+ if flag == nil {
+ t.Fatal("expected --disco flag to be defined")
+ }
}
diff --git a/internal/cli/work.go b/internal/cli/work.go
index 22f0406..d6b989e 100644
--- a/internal/cli/work.go
+++ b/internal/cli/work.go
@@ -10,10 +10,10 @@ import (
"strings"
"time"
- "codeberg.org/snonux/timr/internal/duration"
- "codeberg.org/snonux/timr/internal/timefmt"
- timrTimer "codeberg.org/snonux/timr/internal/timer"
- "codeberg.org/snonux/timr/internal/worktime"
+ "codeberg.org/snonux/timesamurai/internal/duration"
+ "codeberg.org/snonux/timesamurai/internal/timefmt"
+ timesamuraiTimer "codeberg.org/snonux/timesamurai/internal/timer"
+ "codeberg.org/snonux/timesamurai/internal/worktime"
"github.com/spf13/cobra"
)
@@ -32,6 +32,7 @@ func newWorkCmd() *cobra.Command {
cmd.AddCommand(newWorkLogoutCmd())
cmd.AddCommand(newWorkAddCmd())
cmd.AddCommand(newWorkSubCmd())
+ cmd.AddCommand(newWorkDayOffCmd())
cmd.AddCommand(newWorkUseBufferCmd())
cmd.AddCommand(newWorkReportCmd())
cmd.AddCommand(newWorkStatusCmd())
@@ -211,6 +212,39 @@ func newWorkSubCmd() *cobra.Command {
return cmd
}
+func newWorkDayOffCmd() *cobra.Command {
+ var at string
+ var descr string
+
+ cmd := &cobra.Command{
+ Use: "day-off",
+ Short: "Add an 8-hour day-off entry",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ ctx, err := resolveWorkContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ day, err := parseAtOrNow(at)
+ if err != nil {
+ return err
+ }
+
+ entry, err := worktime.AddDayOff(ctx.dbDir, ctx.host, day, descr)
+ if err != nil {
+ return err
+ }
+
+ dayLabel := time.Unix(entry.Epoch, 0).Format("2006-01-02")
+ return printOutput(cmd, fmt.Sprintf("Added day off: 8h on %s", dayLabel))
+ },
+ }
+
+ cmd.Flags().StringVar(&at, "a