fuso 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '079729abca60749f7ed805ad7acad6127d8858781e0a520182e1e3db70d80488'
4
+ data.tar.gz: 5af2c50fe031ea9e551a5d97cb9a2ce3cb6a3e94028306ff896e5bd0aa667cba
5
+ SHA512:
6
+ metadata.gz: 97151b014221fd818cebb75c4c4e0796a5b955d3e08e4ca8ff7227185c165673fc9775d429d441fd5afceba8ced8c133496b3cf902c0f874caab8964e7acc081
7
+ data.tar.gz: 1b2cf8b7a3ca6f212c681150e41dce076d15a9f7f41db4b6aa209c36960106d53ba24ea845d0946557aaa244c52aacd7df4ee2fa9b5885bf6876e40b53bf8bde
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Afonso
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,242 @@
1
+ <h1 align="center">Fuso</h1>
2
+ <p align="center">
3
+ <em>A dead-simple terminal time tracker.<br>One key to start. One key to switch. One key to stop.</em>
4
+ </p>
5
+
6
+ <p align="center">
7
+ <a href="#installation">Install</a> ·
8
+ <a href="#usage">Usage</a> ·
9
+ <a href="#reports">Reports</a> ·
10
+ <a href="#contributing">Contributing</a>
11
+ </p>
12
+
13
+ ---
14
+
15
+ Most time tracking tools are either clunky web apps that demand your attention, or over-engineered CLIs with a dozen subcommands and flags. You just want to press **one key** when you sit down, and another key when you switch tasks. That's it.
16
+
17
+ **Fuso** is a terminal-native time tracker that lives in your terminal and gets out of your way. No accounts. No cloud. No config files to learn. You open it, press `s`, and you're tracking. Press `3` to switch to "Meeting." Press `s` again to stop. Done.
18
+
19
+ Your data is yours - plain JSON files in `~/.fuso/`.
20
+
21
+ ## Screenshots
22
+
23
+ <p align="center">
24
+ <img src="assets/screenshot_tracking.png" alt="Fuso — active tracking" width="480">
25
+ </p>
26
+ <p align="center"><em>Tracking time with live category breakdown and per-category totals.</em></p>
27
+
28
+ <p align="center">
29
+ <img src="assets/screenshot_report.png" alt="Fuso — weekly report" width="480">
30
+ </p>
31
+ <p align="center"><em>Weekly and monthly reports with CSV export.</em></p>
32
+
33
+ ## Features
34
+
35
+ - **One-key workflow** — `s` to start/stop, `1`–`9` to switch categories, `p` to switch projects
36
+ - **Minimalist TUI** — only what matters is on screen, nothing else
37
+ - **Offline & private** — all data stored locally in `~/.fuso/` as plain JSON
38
+ - **Suspend-safe** — if your laptop sleeps or hibernates, the timer stops automatically at the last known time (works in WSL too)
39
+ - **CSV export** — weekly and monthly reports, ready for timesheets or invoicing
40
+ - **Zero config** — sensible defaults on first run, customize via the built-in settings screen
41
+
42
+ ## Installation
43
+
44
+ ### Requirements
45
+
46
+ - **Ruby 3.2+** (Ruby 4.0+ recommended)
47
+ - A terminal with 256-color support
48
+
49
+ ### From RubyGems
50
+
51
+ ```bash
52
+ gem install fuso
53
+ ```
54
+
55
+ That's it. Now run `fuso` from anywhere.
56
+
57
+ ### From source
58
+
59
+ ```bash
60
+ git clone https://github.com/AfonsoNeto/fuso-tui.git
61
+ cd fuso-tui
62
+ bundle install
63
+ bundle exec exe/fuso
64
+ ```
65
+
66
+ ## Usage
67
+
68
+ ### Quick Start
69
+
70
+ 1. Launch `fuso`
71
+ 2. Press `s` to start tracking
72
+ 3. Press `1`–`9` to switch between categories
73
+ 4. Press `s` to stop
74
+ 5. Press `r` to view reports
75
+
76
+ That's it. No flags, no subcommands, no YAML.
77
+
78
+ ### Typical Workflow
79
+
80
+ Imagine you're a developer working on two projects. You open Fuso in the morning:
81
+
82
+ ```
83
+ → Press s to start tracking "Development" on "Project Alpha"
84
+ → A meeting starts — press 3 to switch to "Meeting"
85
+ (Development time is saved automatically)
86
+ → Meeting ends — press 1 to switch back to "Development"
87
+ → Need to help a colleague — press 4 for "Support"
88
+ → End of day — press s to stop
89
+ → Press r then e to export your timesheet as CSV
90
+ ```
91
+
92
+ Every category switch is a single keypress. No interruptions. No context switching away from your terminal.
93
+
94
+ ### Use Cases
95
+
96
+ | Scenario | How Fuso Helps |
97
+ |----------|---------------|
98
+ | **Freelancer billing** | Track hours per project/category, export CSV for invoicing |
99
+ | **Team standups** | Glance at yesterday's breakdown before standup |
100
+ | **Focus tracking** | See where your time actually goes vs. where you think it goes |
101
+ | **Contractor timesheets** | Monthly reports with weekly breakdown, ready for submission |
102
+
103
+ ## Keyboard Shortcuts
104
+
105
+ ### Main Screen
106
+
107
+ | Key | Action |
108
+ |-----|--------|
109
+ | `s` / `Space` | Start or stop the timer |
110
+ | `1`–`9` | Switch to category by number |
111
+ | `p` | Cycle through projects |
112
+ | `m` | Open settings (manage projects & categories) |
113
+ | `r` | Open reports |
114
+ | `q` / `Ctrl+C` | Quit (active session is saved automatically) |
115
+
116
+ ### Settings Screen
117
+
118
+ | Key | Action |
119
+ |-----|--------|
120
+ | `↑` / `↓` | Navigate list |
121
+ | `Tab` | Switch between projects and categories |
122
+ | `a` | Add new item |
123
+ | `d` | Set selected item as default |
124
+ | `x` | Delete selected item |
125
+ | `Esc` | Back to main screen |
126
+
127
+ ### Reports Screen
128
+
129
+ | Key | Action |
130
+ |-----|--------|
131
+ | `Tab` | Toggle between weekly and monthly view |
132
+ | `←` / `→` | Navigate between weeks or months |
133
+ | `e` | Export current view as CSV |
134
+ | `Esc` | Back to main screen |
135
+
136
+ ## Reports
137
+
138
+ ### In-App Reports
139
+
140
+ Press `r` to open the report view. Use `Tab` to switch between weekly and monthly breakdowns. Navigate with `←` / `→`.
141
+
142
+ ### CSV Export
143
+
144
+ Press `e` on the report screen to export. Files are saved to `~/.fuso/exports/`:
145
+
146
+ ```
147
+ ~/.fuso/exports/fuso_weekly_2026-W25.csv
148
+ ~/.fuso/exports/fuso_monthly_2026-06.csv
149
+ ```
150
+
151
+ The monthly CSV includes a weekly breakdown with columns for each week-ending date, matching a standard timesheet format.
152
+
153
+ ### CLI Reports
154
+
155
+ You can also view reports without the TUI:
156
+
157
+ ```bash
158
+ fuso report weekly # Current week summary
159
+ fuso report monthly # Current month summary
160
+ ```
161
+
162
+ ## Data Storage
163
+
164
+ All data lives in `~/.fuso/`:
165
+
166
+ ```
167
+ ~/.fuso/
168
+ ├── config.json # Projects, categories, defaults
169
+ ├── sessions.json # All time entries, keyed by date
170
+ ├── heartbeat # Active tracking timestamp (auto-cleaned)
171
+ └── exports/ # Exported CSV files
172
+ ```
173
+
174
+ Everything is plain JSON. You can read it, edit it, back it up, or sync it however you want.
175
+
176
+ ## How Suspend Recovery Works
177
+
178
+ If your laptop goes to sleep, hibernates, or suspends — or if you close the lid and come back later — Fuso won't count the sleep time as work.
179
+
180
+ While the timer is running, Fuso writes a heartbeat timestamp to `~/.fuso/heartbeat` every 10 seconds. When it wakes up and notices the gap is too large (>60s), it saves the session at the last known heartbeat time and stops the timer.
181
+
182
+ This works everywhere: native Linux, WSL, macOS — no D-Bus, no systemd, no OS-specific APIs needed.
183
+
184
+ ## Built With
185
+
186
+ - [Ruby](https://www.ruby-lang.org/) — simple, readable, gets the job done
187
+ - [Charm Ruby](https://charm-ruby.dev/) — `bubbletea` (Elm Architecture TUI), `lipgloss` (styling), `bubbles` (components)
188
+
189
+ ## Contributing
190
+
191
+ Contributions are welcome! Whether it's bug fixes, new features, documentation improvements, or just ideas — all are appreciated.
192
+
193
+ ### Getting Started
194
+
195
+ ```bash
196
+ # Clone the repo
197
+ git clone https://github.com/AfonsoNeto/fuso-tui.git
198
+ cd fuso-tui
199
+
200
+ # Install dependencies
201
+ bundle install
202
+
203
+ # Run the test suite
204
+ bundle exec rake test
205
+
206
+ # Launch the app
207
+ bundle exec exe/fuso
208
+ ```
209
+
210
+ ### Running Tests
211
+
212
+ The project uses [Minitest](https://github.com/minitest/minitest). All tests run in an isolated temporary directory so your actual `~/.fuso/` data is never touched.
213
+
214
+ ```bash
215
+ bundle exec rake test
216
+ ```
217
+
218
+ ### Project Structure
219
+
220
+ ```
221
+ lib/fuso/
222
+ ├── app.rb # Main Bubbletea model (Elm Architecture)
223
+ ├── config.rb # Config loading & persistence
224
+ ├── session.rb # Time entry recording & queries
225
+ ├── heartbeat.rb # Suspend detection via heartbeat file
226
+ ├── styles.rb # Lipgloss style definitions
227
+ └── views/
228
+ ├── main_view.rb # Timer screen (idle + tracking)
229
+ ├── settings_view.rb # Project/category management
230
+ └── report_view.rb # Weekly & monthly reports + CSV export
231
+ ```
232
+
233
+ ### Guidelines
234
+
235
+ - Keep it minimal. Every feature should earn its screen space.
236
+ - Follow the Elm Architecture pattern (`init` → `update` → `view`).
237
+ - Add tests for new functionality.
238
+ - No external services, no network calls, no accounts.
239
+
240
+ ## License
241
+
242
+ This project is licensed under the [MIT License](LICENSE).
Binary file
Binary file
Binary file
data/exe/fuso ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "fuso"
5
+
6
+ if ARGV.empty?
7
+ # Launch TUI
8
+ Bubbletea.run(Fuso::App.new, alt_screen: true)
9
+ elsif ARGV[0] == "report"
10
+ # CLI report mode
11
+ config = Fuso::Config.load
12
+ session = Fuso::Session.load
13
+ require "date"
14
+
15
+ case ARGV[1]
16
+ when "weekly"
17
+ date = Date.today
18
+ totals = session.weekly_breakdown(date)
19
+ monday = date - ((date.wday + 6) % 7)
20
+ sunday = monday + 6
21
+
22
+ puts "Fuso — Weekly Report"
23
+ puts "Week #{date.cweek}: #{monday.strftime('%b %d')} — #{sunday.strftime('%b %d, %Y')}"
24
+ puts ""
25
+ puts "#{'PROJECT / CATEGORY'.ljust(35)} #{'TOTAL'.rjust(8)}"
26
+ puts "─" * 45
27
+
28
+ grand_total = 0
29
+ totals.sort_by { |k, _| k }.each do |(project, category), seconds|
30
+ label = "#{project} / #{category}".ljust(35)
31
+ time = Fuso::Session.format_duration_hm(seconds).rjust(8)
32
+ puts "#{label} #{time}"
33
+ grand_total += seconds
34
+ end
35
+
36
+ puts "─" * 45
37
+ puts "#{'TOTAL'.ljust(35)} #{Fuso::Session.format_duration_hm(grand_total).rjust(8)}"
38
+ when "monthly"
39
+ date = Date.today
40
+ data = session.monthly_breakdown(date)
41
+ breakdown = data[:breakdown]
42
+ num_weeks = data[:num_weeks]
43
+
44
+ puts "Fuso — Monthly Report"
45
+ puts date.strftime("%B %Y")
46
+ puts ""
47
+
48
+ header = "PROJECT / CATEGORY".ljust(30)
49
+ (1..num_weeks).each { |w| header += "W#{w}".rjust(8) }
50
+ header += "TOTAL".rjust(10)
51
+ puts header
52
+ puts "─" * (30 + num_weeks * 8 + 10)
53
+
54
+ week_totals = Hash.new(0)
55
+ grand_total = 0
56
+
57
+ breakdown.sort_by { |k, _| k }.each do |(project, category), weeks|
58
+ label = "#{project} / #{category}"
59
+ label = label.length > 29 ? label[0..26] + "..." : label.ljust(30)
60
+ row = label
61
+
62
+ row_total = 0
63
+ (1..num_weeks).each do |w|
64
+ seconds = weeks[w]
65
+ row_total += seconds
66
+ week_totals[w] += seconds
67
+ time_str = seconds > 0 ? Fuso::Session.format_duration_hm(seconds) : "—"
68
+ row += time_str.rjust(8)
69
+ end
70
+
71
+ grand_total += row_total
72
+ row += Fuso::Session.format_duration_hm(row_total).rjust(10)
73
+ puts row
74
+ end
75
+
76
+ puts "─" * (30 + num_weeks * 8 + 10)
77
+ total_row = "TOTAL".ljust(30)
78
+ (1..num_weeks).each do |w|
79
+ seconds = week_totals[w]
80
+ time_str = seconds > 0 ? Fuso::Session.format_duration_hm(seconds) : "—"
81
+ total_row += time_str.rjust(8)
82
+ end
83
+ total_row += Fuso::Session.format_duration_hm(grand_total).rjust(10)
84
+ puts total_row
85
+ else
86
+ puts "Usage: fuso report [weekly|monthly]"
87
+ end
88
+ else
89
+ puts "Usage: fuso [report weekly|monthly]"
90
+ end