harvest-worklog 0.7.0 → 0.8.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd656e6ca496a7a7a5d08e0e1f407c5aaf030b80c75751abd354ce1259a3806d
4
- data.tar.gz: cf38ac55098c083668f3df37774298c21b6329c39a6d017d1968fce9227bfd30
3
+ metadata.gz: c7177cf851e2cf1de7dea85c719e9482cddb7e75b205c4579b3955044dfd2f90
4
+ data.tar.gz: 55080b09e031a6fa8076fca5e1a51825e583c628a619f63ef04cc442af117b03
5
5
  SHA512:
6
- metadata.gz: 72a0dc46a8551f9f3a6e8efd7bffd6ee9a2bbaf34021f86cdd98c0dc356d08edd88e96aa0da3e992b59f7ec5794f1656f2a3c785ebf7bc002f99c6a0f06e550b
7
- data.tar.gz: 8fae9c3ecd9f91ac43280b854715532c4ea86221a9381793fea2674800e54d4577962e7a1adc8829a545544ff6b578727aee1396f979ccc4c02c554ad68f9121
6
+ metadata.gz: 589caf5b05eb1770cc06ac4eb9554c94d20218275d7cfb3191ceff09d6d4fb6f62e237d0722504e2e4542b3f99505064f7486228ec51aca4580bb9bce90a5165
7
+ data.tar.gz: 9aa1ba60998427c430e1ee7d8f9dde6500d864ef6ce1921303aec3192359ae34df49d0bdcd3c4c90c9b17ad42502818ec101c020c111591e71d8fa8ca163366d
data/README.md CHANGED
@@ -23,6 +23,7 @@ Personal Access Tokens are the intended Harvest authentication method for person
23
23
  harvest-worklog time-off FROM TO --project NAME --task NAME [options]
24
24
  harvest-worklog work-entry DATE --project NAME --task NAME --hours HOURS --notes NOTES [options]
25
25
  harvest-worklog aggregate FROM TO [--project NAME] [--task NAME]
26
+ harvest-worklog timesheet DATE --project NAME [--task NAME]
26
27
  ```
27
28
 
28
29
  ### Time off
@@ -57,6 +58,16 @@ harvest-worklog aggregate 2026-07-17 2026-07-19 \
57
58
 
58
59
  The `harvest_time_aggregates` OMP tool exposes the same optional filters and is approval-gated as a read.
59
60
 
61
+ ### Daily timesheets
62
+
63
+ The `timesheet` command reads the authenticated user's compact daily Harvest view: task totals, entry durations when a task has multiple entries, and multiline notes. `DATE` accepts `today`, `yesterday`, or an ISO date in the CLI's local timezone.
64
+
65
+ ```bash
66
+ harvest-worklog timesheet today --project WRAP
67
+ ```
68
+
69
+ OMP exposes the same CLI-only path through `/harvest-worklog today WRAP` and the read-only `harvest_time_sheet` tool. The wrapper never calls Harvest directly. A later, separate feature may upload reviewed content through the Harvest API; keep that write path separate from this read-only formatter.
70
+
60
71
  ## OMP settings
61
72
 
62
73
  - `defaultHours`: hours per business day when a time-off tool call omits `hours`; defaults to `7`.
@@ -40,14 +40,16 @@ module HarvestWorklog
40
40
  raise Error, "FROM and TO are required" unless dates.length == 2
41
41
  end
42
42
 
43
- def self.fetch_entries(client, from:, to:)
43
+ def self.fetch_entries(client, from:, to:, user_id: nil)
44
44
  entries = []
45
45
  page = 1
46
46
  loop do
47
+ params = { from: from.iso8601, to: to.iso8601, page:, per_page: PER_PAGE }
48
+ params[:user_id] = user_id if user_id
47
49
  response = client.request(
48
50
  :get,
49
51
  "/v2/time_entries",
50
- params: { from: from.iso8601, to: to.iso8601, page:, per_page: PER_PAGE }
52
+ params:
51
53
  )
52
54
  entries.concat(response.fetch("time_entries"))
53
55
  page = response["next_page"]
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarvestWorklog
4
+ class TimesheetCLI
5
+ def self.run(arguments, output: $stdout, error: $stderr, client: nil, today: Date.today)
6
+ options = {}
7
+ parser = option_parser(options)
8
+ dates = parser.parse(arguments)
9
+ validate!(dates, options)
10
+
11
+ spent_date = resolve_date(dates.first, today:)
12
+ client ||= Marlens::HarvestApiV2::Client.from_environment
13
+ user_id = current_user_id(client)
14
+ entries = AggregateCLI.fetch_entries(client, from: spent_date, to: spent_date, user_id:).select { |entry| AggregateCLI.matches?(entry, options) }
15
+ print_timesheet(output, entries, spent_date:, requested_project: options[:project])
16
+ 0
17
+ rescue Error, Marlens::HarvestApiV2::Error, OptionParser::ParseError, Date::Error => e
18
+ error.puts "Error: #{e.message}"
19
+ error.puts parser if parser
20
+ 1
21
+ end
22
+
23
+ def self.option_parser(options)
24
+ OptionParser.new do |opts|
25
+ opts.banner = "Usage: harvest-worklog timesheet DATE --project NAME [--task NAME]"
26
+ opts.on("--project NAME", "Filter by Harvest project name") { |value| options[:project] = value }
27
+ opts.on("--task NAME", "Filter by Harvest task name") { |value| options[:task] = value }
28
+ opts.on("-h", "--help", "Show this help") do
29
+ puts opts
30
+ exit 0
31
+ end
32
+ end
33
+ end
34
+
35
+ def self.resolve_date(value, today: Date.today)
36
+ case value.downcase
37
+ when "today" then today
38
+ when "yesterday" then today - 1
39
+ else Date.iso8601(value)
40
+ end
41
+ end
42
+
43
+ def self.current_user_id(client)
44
+ client.request(:get, "/v2/users/me", params: {}).fetch("id")
45
+ end
46
+
47
+ def self.validate!(dates, options)
48
+ raise Error, "DATE is required" unless dates.length == 1
49
+ raise Error, "--project is required" unless options[:project] && !options[:project].empty?
50
+ end
51
+
52
+ def self.print_timesheet(output, entries, spent_date:, requested_project:)
53
+ project = entries.first&.dig("project", "name") || requested_project
54
+ output.puts "#{project} · #{spent_date.strftime("%a, %b %-d")} · #{HarvestWorklog.display_hours(AggregateCLI.total_hours(entries))}h"
55
+ if entries.empty?
56
+ output.puts
57
+ output.puts "No time entries."
58
+ return
59
+ end
60
+
61
+ entries.group_by { |entry| entry.dig("task", "name") }.sort_by { |task, _| task.downcase }.each do |task, task_entries|
62
+ output.puts
63
+ output.puts "#{task} · #{HarvestWorklog.display_hours(AggregateCLI.total_hours(task_entries))}h"
64
+ if task_entries.one?
65
+ print_notes(output, task_entries.first["notes"].to_s, indent: 2)
66
+ else
67
+ task_entries.each do |entry|
68
+ output.puts " #{HarvestWorklog.display_hours(Float(entry.fetch("hours")))}h"
69
+ print_notes(output, entry["notes"].to_s, indent: 4)
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def self.print_notes(output, notes, indent:)
76
+ lines = notes.lines(chomp: true).reject(&:empty?)
77
+ lines = ["(no notes)"] if lines.empty?
78
+ lines.each { |line| output.puts "#{" " * indent}#{line}" }
79
+ end
80
+ end
81
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HarvestWorklog
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
@@ -36,11 +36,13 @@ module HarvestWorklog
36
36
  WorkEntryCLI.run(command_arguments, output:, error:, client:)
37
37
  when "aggregate"
38
38
  AggregateCLI.run(command_arguments, output:, error:, client:)
39
+ when "timesheet"
40
+ TimesheetCLI.run(command_arguments, output:, error:, client:)
39
41
  when "-h", "--help"
40
42
  output.puts usage
41
43
  0
42
44
  else
43
- error.puts "Error: choose time-off, work-entry, or aggregate"
45
+ error.puts "Error: choose time-off, work-entry, aggregate, or timesheet"
44
46
  error.puts usage
45
47
  1
46
48
  end
@@ -52,11 +54,13 @@ module HarvestWorklog
52
54
  harvest-worklog time-off FROM TO --project NAME --task NAME [options]
53
55
  harvest-worklog work-entry DATE --project NAME --task NAME --hours HOURS --notes NOTES [options]
54
56
  harvest-worklog aggregate FROM TO [--project NAME] [--task NAME]
57
+ harvest-worklog timesheet DATE --project NAME [--task NAME]
55
58
 
56
59
  Commands:
57
60
  time-off Create one entry per local business day in a date range.
58
61
  work-entry Create one reviewed ordinary-work entry.
59
62
  aggregate Read time-entry totals without writing Harvest records.
63
+ timesheet Read a compact daily project timesheet without writing Harvest records.
60
64
  USAGE
61
65
  end
62
66
  end
@@ -170,3 +174,4 @@ end
170
174
 
171
175
  require "harvest_worklog/work_entry_cli"
172
176
  require "harvest_worklog/aggregate_cli"
177
+ require "harvest_worklog/timesheet_cli"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: harvest-worklog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marlen Brunner
@@ -63,6 +63,7 @@ files:
63
63
  - harvest-worklog.rb
64
64
  - lib/harvest_worklog.rb
65
65
  - lib/harvest_worklog/aggregate_cli.rb
66
+ - lib/harvest_worklog/timesheet_cli.rb
66
67
  - lib/harvest_worklog/version.rb
67
68
  - lib/harvest_worklog/work_entry_cli.rb
68
69
  homepage: https://github.com/klondikemarlen/harvest-worklog