reclaim 0.2.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.
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reclaim
4
+ # Represents a Reclaim task with all its properties and methods
5
+ class Task
6
+ # Task priority levels matching Reclaim API
7
+ PRIORITIES = {
8
+ p1: 'P1',
9
+ p2: 'P2',
10
+ p3: 'P3',
11
+ p4: 'P4'
12
+ }.freeze
13
+
14
+ # Task status values
15
+ STATUSES = %w[
16
+ NEW SCHEDULED IN_PROGRESS COMPLETE ARCHIVED CANCELLED
17
+ ].freeze
18
+
19
+ # Event colors for calendar display
20
+ EVENT_COLORS = %w[
21
+ BLUE GREEN YELLOW ORANGE RED PURPLE PINK BROWN GRAY
22
+ ].freeze
23
+
24
+ # All task attributes with accessor methods
25
+ attr_accessor :id, :title, :notes, :due_date, :priority, :duration,
26
+ :min_chunk_size, :max_chunk_size, :min_work_duration,
27
+ :max_work_duration, :snooze_until, :start, :time_scheme_id,
28
+ :always_private, :event_category, :event_color, :status,
29
+ :created_at, :updated_at, :deleted
30
+
31
+ def initialize(attributes = {})
32
+ # Convert API field names to internal format
33
+ converted_attrs = {}
34
+ attributes.each do |key, value|
35
+ case key.to_s
36
+ when 'timeChunksRequired'
37
+ converted_attrs['duration'] = (value / 4.0) # Convert from 15-min chunks to hours
38
+ when 'timeSchemeId'
39
+ converted_attrs['time_scheme_id'] = value
40
+ when 'alwaysPrivate'
41
+ converted_attrs['always_private'] = value
42
+ when 'eventCategory'
43
+ converted_attrs['event_category'] = value
44
+ when 'eventColor'
45
+ converted_attrs['event_color'] = value
46
+ when 'minChunkSize'
47
+ converted_attrs['min_chunk_size'] = (value / 4.0) if value
48
+ when 'maxChunkSize'
49
+ converted_attrs['max_chunk_size'] = (value / 4.0) if value
50
+ when 'due'
51
+ converted_attrs['due_date'] = value
52
+ else
53
+ converted_attrs[key.to_s] = value
54
+ end
55
+ end
56
+
57
+ converted_attrs.each do |key, value|
58
+ setter = "#{key}="
59
+ public_send(setter, value) if respond_to?(setter)
60
+ end
61
+
62
+ # Set defaults
63
+ @priority ||= :p3
64
+ @duration ||= 1.0
65
+ @always_private ||= false
66
+ @deleted ||= false
67
+ @status ||= 'NEW'
68
+ end
69
+
70
+ # Convert task to hash for API requests
71
+ def to_h
72
+ {
73
+ id: id,
74
+ title: title,
75
+ notes: notes,
76
+ due_date: due_date,
77
+ priority: priority_for_api,
78
+ duration: duration,
79
+ min_chunk_size: min_chunk_size,
80
+ max_chunk_size: max_chunk_size,
81
+ min_work_duration: min_work_duration,
82
+ max_work_duration: max_work_duration,
83
+ snooze_until: snooze_until,
84
+ start: start,
85
+ time_scheme_id: time_scheme_id,
86
+ always_private: always_private,
87
+ event_category: event_category,
88
+ event_color: event_color,
89
+ status: status,
90
+ created_at: created_at,
91
+ updated_at: updated_at,
92
+ deleted: deleted
93
+ }.compact
94
+ end
95
+
96
+ # Check if task is active (not deleted and not archived/cancelled)
97
+ def active?
98
+ !deleted && !%w[ARCHIVED CANCELLED].include?(status)
99
+ end
100
+
101
+ # Check if task is completed
102
+ def completed?
103
+ %w[COMPLETE ARCHIVED].include?(status)
104
+ end
105
+
106
+ # Check if task is overdue
107
+ def overdue?
108
+ return false unless due_date && active?
109
+
110
+ due_time = parse_datetime(due_date)
111
+ due_time && due_time < Time.now
112
+ end
113
+
114
+ # Get formatted due date string
115
+ def due_date_formatted
116
+ return nil unless due_date
117
+
118
+ parsed = parse_datetime(due_date)
119
+ parsed&.strftime('%Y-%m-%d %H:%M')
120
+ end
121
+
122
+ # Get priority as symbol
123
+ def priority_symbol
124
+ case priority
125
+ when 'P1' then :p1
126
+ when 'P2' then :p2
127
+ when 'P3' then :p3
128
+ when 'P4' then :p4
129
+ else :p3
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ # Convert priority symbol to API format
136
+ def priority_for_api
137
+ PRIORITIES[priority] || PRIORITIES[priority_symbol] || 'P3'
138
+ end
139
+
140
+ # Parse various datetime formats
141
+ def parse_datetime(date_string)
142
+ return nil unless date_string
143
+
144
+ Time.parse(date_string.to_s)
145
+ rescue ArgumentError
146
+ nil
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'date'
5
+
6
+ module Reclaim
7
+ # Utility methods for common operations
8
+ module Utils
9
+ # Format task for display
10
+ def self.format_task(task)
11
+ status_icon = task.completed? ? "✓" : "○"
12
+ due_str = task.due_date ? " (due: #{task.due_date_formatted})" : ""
13
+
14
+ "#{status_icon} #{task.title}#{due_str}\n" \
15
+ " ID: #{task.id} | Priority: #{task.priority} | Status: #{task.status}"
16
+ end
17
+
18
+ # Format task list for display
19
+ def self.format_task_list(tasks, title = "Tasks")
20
+ return "No tasks found." if tasks.empty?
21
+
22
+ output = "\n#{title}:\n"
23
+ output += "-" * 50 + "\n"
24
+
25
+ tasks.each do |task|
26
+ output += format_task(task) + "\n"
27
+ end
28
+
29
+ output += "\nTotal: #{tasks.length} tasks\n"
30
+ output
31
+ end
32
+
33
+ # Parse various date formats into ISO string
34
+ def self.parse_date(date_input)
35
+ return nil unless date_input
36
+
37
+ case date_input
38
+ when Time
39
+ date_input.iso8601
40
+ when Date
41
+ date_input.to_time.iso8601
42
+ when String
43
+ # Try to parse and convert to ISO format
44
+ Time.parse(date_input).iso8601
45
+ else
46
+ date_input.to_s
47
+ end
48
+ rescue ArgumentError
49
+ date_input.to_s
50
+ end
51
+
52
+ # Format datetime for API (UTC with Z suffix like the Python SDK)
53
+ def self.format_datetime_for_api(date_input)
54
+ return nil unless date_input
55
+
56
+ case date_input
57
+ when Time
58
+ date_input.utc.iso8601
59
+ when Date
60
+ date_input.to_time.utc.iso8601
61
+ when String
62
+ # Try to parse and convert to UTC ISO format with Z suffix
63
+ Time.parse(date_input).utc.iso8601
64
+ else
65
+ Time.parse(date_input.to_s).utc.iso8601
66
+ end
67
+ rescue ArgumentError
68
+ date_input.to_s
69
+ end
70
+
71
+ # Validate priority value
72
+ def self.validate_priority(priority)
73
+ return :p3 unless priority
74
+
75
+ priority = priority.to_s.downcase.to_sym
76
+ Task::PRIORITIES.key?(priority) ? priority : :p3
77
+ end
78
+
79
+ # Validate duration value
80
+ def self.validate_duration(duration)
81
+ return 1.0 unless duration
82
+
83
+ duration = duration.to_f
84
+ duration.positive? ? duration : 1.0
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reclaim
4
+ VERSION = '0.2.0'
5
+ end
data/lib/reclaim.rb ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Reclaim API Ruby Library
4
+ #
5
+ # A comprehensive Ruby library for interacting with the Reclaim.ai API.
6
+ # Provides task management functionality with proper error handling,
7
+ # time scheme resolution, and caching.
8
+ #
9
+ # Environment Variables:
10
+ # RECLAIM_API_KEY - Your Reclaim API token
11
+ #
12
+ # Usage:
13
+ # require 'reclaim'
14
+ #
15
+ # client = Reclaim::Client.new
16
+ #
17
+ # # Create a task
18
+ # task = client.create_task(
19
+ # title: 'Important Work',
20
+ # due_date: '2025-08-15T17:00:00-04:00',
21
+ # priority: :p1,
22
+ # duration: 2.0,
23
+ # time_scheme: 'work'
24
+ # )
25
+ #
26
+ # # List tasks
27
+ # tasks = client.list_tasks(filter: :active)
28
+ #
29
+ # # Update a task
30
+ # client.update_task(task.id, title: 'Updated Title')
31
+ #
32
+ # # Complete a task
33
+ # client.complete_task(task.id)
34
+
35
+ # Optionally load environment variables from .env files if dotenv is available.
36
+ # This maintains zero runtime dependencies while supporting convenient local development.
37
+ # Dotenv.load does not overwrite existing ENV vars, so shell variables take precedence.
38
+ # .env files are loaded from the current working directory.
39
+ begin
40
+ require 'dotenv'
41
+ Dotenv.load('.env.local', '.env')
42
+ rescue LoadError
43
+ # dotenv not installed, skip .env loading
44
+ rescue => e
45
+ # Malformed .env file or other dotenv error - warn but don't crash
46
+ warn "Warning: Could not load .env file: #{e.message}" if $VERBOSE
47
+ end
48
+
49
+ require_relative 'reclaim/version'
50
+ require_relative 'reclaim/errors'
51
+ require_relative 'reclaim/task'
52
+ require_relative 'reclaim/utils'
53
+ require_relative 'reclaim/client'
54
+ require_relative 'reclaim/cli'
55
+
56
+ module Reclaim
57
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reclaim
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Benjamin Jackson
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: dotenv
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.8'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.8'
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: yard
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.9'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.9'
68
+ description: A comprehensive Ruby library for interacting with the Reclaim.ai API.
69
+ Provides task management functionality with proper error handling, time scheme resolution,
70
+ caching, and a command-line interface.
71
+ email:
72
+ - ben@hearmeout.co
73
+ executables:
74
+ - reclaim
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - CHANGELOG.md
79
+ - LICENSE.txt
80
+ - README.md
81
+ - bin/reclaim
82
+ - lib/reclaim.rb
83
+ - lib/reclaim/cli.rb
84
+ - lib/reclaim/client.rb
85
+ - lib/reclaim/errors.rb
86
+ - lib/reclaim/task.rb
87
+ - lib/reclaim/utils.rb
88
+ - lib/reclaim/version.rb
89
+ homepage: https://github.com/benjaminjackson/reclaim-ruby
90
+ licenses:
91
+ - MIT
92
+ metadata:
93
+ homepage_uri: https://github.com/benjaminjackson/reclaim-ruby
94
+ source_code_uri: https://github.com/benjaminjackson/reclaim-ruby
95
+ changelog_uri: https://github.com/benjaminjackson/reclaim-ruby/blob/main/CHANGELOG.md
96
+ bug_tracker_uri: https://github.com/benjaminjackson/reclaim-ruby/issues
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 3.0.0
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 3.7.2
112
+ specification_version: 4
113
+ summary: Ruby client for Reclaim.ai API
114
+ test_files: []