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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +46 -0
- data/LICENSE.txt +21 -0
- data/README.md +367 -0
- data/bin/reclaim +6 -0
- data/lib/reclaim/cli.rb +297 -0
- data/lib/reclaim/client.rb +337 -0
- data/lib/reclaim/errors.rb +26 -0
- data/lib/reclaim/task.rb +149 -0
- data/lib/reclaim/utils.rb +87 -0
- data/lib/reclaim/version.rb +5 -0
- data/lib/reclaim.rb +57 -0
- metadata +114 -0
data/lib/reclaim/task.rb
ADDED
|
@@ -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
|
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: []
|