hiiro 0.1.81 → 0.1.82
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 +4 -4
- data/bin/h-pr +115 -8
- data/lib/hiiro/version.rb +1 -1
- metadata +1 -8
- data/bin/h-home +0 -62
- data/bin/h-html +0 -380
- data/bin/h-mic +0 -93
- data/bin/h-note +0 -119
- data/bin/h-remind +0 -614
- data/bin/h-serve +0 -6
- data/bin/h-video +0 -523
data/bin/h-remind
DELETED
|
@@ -1,614 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require 'yaml'
|
|
4
|
-
require 'fileutils'
|
|
5
|
-
require 'time'
|
|
6
|
-
require 'tempfile'
|
|
7
|
-
require 'hiiro'
|
|
8
|
-
|
|
9
|
-
class ReminderManager
|
|
10
|
-
REMINDERS_FILE = File.join(Dir.home, '.config/hiiro/reminders.yml')
|
|
11
|
-
REMINDER_TEMPLATE = {
|
|
12
|
-
'message' => '',
|
|
13
|
-
'interval' => '1h',
|
|
14
|
-
'sound' => nil,
|
|
15
|
-
'enabled' => true,
|
|
16
|
-
'once' => false,
|
|
17
|
-
'trigger_at' => nil,
|
|
18
|
-
'last_triggered' => nil,
|
|
19
|
-
'created_at' => Time.now.iso8601,
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
class Reminder
|
|
23
|
-
attr_accessor :message, :interval, :sound, :enabled, :once, :trigger_at, :last_triggered, :created_at
|
|
24
|
-
|
|
25
|
-
def initialize(message:, interval: '1h', sound: nil, enabled: true, once: false, trigger_at: nil, last_triggered: nil, created_at: Time.now.iso8601)
|
|
26
|
-
@message = message
|
|
27
|
-
@interval = interval
|
|
28
|
-
@sound = sound
|
|
29
|
-
@enabled = enabled
|
|
30
|
-
@once = once
|
|
31
|
-
@trigger_at = trigger_at
|
|
32
|
-
@last_triggered = last_triggered
|
|
33
|
-
@created_at = created_at
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def self.from_hash(hash)
|
|
37
|
-
new(
|
|
38
|
-
message: hash['message'],
|
|
39
|
-
interval: hash['interval'] || '1h',
|
|
40
|
-
sound: hash['sound'],
|
|
41
|
-
enabled: hash['enabled'] != false,
|
|
42
|
-
once: hash['once'] == true,
|
|
43
|
-
trigger_at: hash['trigger_at'],
|
|
44
|
-
last_triggered: hash['last_triggered'],
|
|
45
|
-
created_at: hash['created_at'] || Time.now.iso8601
|
|
46
|
-
)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def to_h
|
|
50
|
-
{
|
|
51
|
-
'message' => @message,
|
|
52
|
-
'interval' => @interval,
|
|
53
|
-
'sound' => @sound,
|
|
54
|
-
'enabled' => @enabled,
|
|
55
|
-
'once' => @once,
|
|
56
|
-
'trigger_at' => @trigger_at,
|
|
57
|
-
'last_triggered' => @last_triggered,
|
|
58
|
-
'created_at' => @created_at
|
|
59
|
-
}
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def to_yaml
|
|
63
|
-
to_h.to_yaml
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def interval_seconds
|
|
67
|
-
parse_interval(@interval)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def parse_interval(str)
|
|
71
|
-
return 3600 unless str # default 1 hour
|
|
72
|
-
|
|
73
|
-
match = str.to_s.match(/^(\d+)([smhdw])$/i)
|
|
74
|
-
return 3600 unless match
|
|
75
|
-
|
|
76
|
-
num = match[1].to_i
|
|
77
|
-
unit = match[2].downcase
|
|
78
|
-
|
|
79
|
-
case unit
|
|
80
|
-
when 's' then num
|
|
81
|
-
when 'm' then num * 60
|
|
82
|
-
when 'h' then num * 3600
|
|
83
|
-
when 'd' then num * 86400
|
|
84
|
-
when 'w' then num * 604800
|
|
85
|
-
else 3600
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def one_time?
|
|
90
|
-
@once == true
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def should_trigger?
|
|
94
|
-
return false unless @enabled
|
|
95
|
-
|
|
96
|
-
if one_time?
|
|
97
|
-
# One-time reminder: check if trigger_at time has passed
|
|
98
|
-
return false if @last_triggered # already triggered
|
|
99
|
-
return false unless @trigger_at
|
|
100
|
-
|
|
101
|
-
trigger_time = Time.parse(@trigger_at) rescue nil
|
|
102
|
-
return false unless trigger_time
|
|
103
|
-
|
|
104
|
-
Time.now >= trigger_time
|
|
105
|
-
else
|
|
106
|
-
# Recurring reminder: check interval since last trigger
|
|
107
|
-
return true if @last_triggered.nil?
|
|
108
|
-
|
|
109
|
-
last_time = Time.parse(@last_triggered) rescue Time.at(0)
|
|
110
|
-
Time.now - last_time >= interval_seconds
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def trigger!
|
|
115
|
-
@last_triggered = Time.now.iso8601
|
|
116
|
-
# For one-time reminders, disable after triggering
|
|
117
|
-
@enabled = false if one_time?
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def display_string(index = nil)
|
|
121
|
-
num = index ? "#{(index + 1).to_s.rjust(3)}." : ""
|
|
122
|
-
status = @enabled ? "[ON]" : "[OFF]"
|
|
123
|
-
sound_str = @sound ? " (#{@sound})" : ""
|
|
124
|
-
|
|
125
|
-
if one_time?
|
|
126
|
-
if @trigger_at
|
|
127
|
-
trigger_time = Time.parse(@trigger_at) rescue nil
|
|
128
|
-
time_str = trigger_time ? trigger_time.strftime('%Y-%m-%d %H:%M') : @trigger_at
|
|
129
|
-
"#{num} #{status} [ONCE @ #{time_str}] #{@message}#{sound_str}".strip
|
|
130
|
-
else
|
|
131
|
-
"#{num} #{status} [ONCE] #{@message}#{sound_str}".strip
|
|
132
|
-
end
|
|
133
|
-
else
|
|
134
|
-
"#{num} #{status} [#{@interval}] #{@message}#{sound_str}".strip
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Parse time argument - returns [is_once, trigger_at, interval]
|
|
140
|
-
def self.parse_time_arg(time_str)
|
|
141
|
-
return [false, nil, '1h'] if time_str.nil?
|
|
142
|
-
|
|
143
|
-
# Check for absolute time format: YYYYmmddHHMM
|
|
144
|
-
if time_str =~ /^(\d{12})$/
|
|
145
|
-
year = $1[0..3].to_i
|
|
146
|
-
month = $1[4..5].to_i
|
|
147
|
-
day = $1[6..7].to_i
|
|
148
|
-
hour = $1[8..9].to_i
|
|
149
|
-
min = $1[10..11].to_i
|
|
150
|
-
trigger_at = Time.new(year, month, day, hour, min).iso8601
|
|
151
|
-
return [true, trigger_at, nil]
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# Check for relative interval format: 15m, 2h, etc.
|
|
155
|
-
if time_str =~ /^(\d+)([smhdw])$/i
|
|
156
|
-
num = $1.to_i
|
|
157
|
-
unit = $2.downcase
|
|
158
|
-
seconds = case unit
|
|
159
|
-
when 's' then num
|
|
160
|
-
when 'm' then num * 60
|
|
161
|
-
when 'h' then num * 3600
|
|
162
|
-
when 'd' then num * 86400
|
|
163
|
-
when 'w' then num * 604800
|
|
164
|
-
else num * 60
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# This is a one-time reminder at a relative time
|
|
168
|
-
trigger_at = (Time.now + seconds).iso8601
|
|
169
|
-
return [true, trigger_at, time_str]
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# Default: treat as recurring interval
|
|
173
|
-
[false, nil, time_str]
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
attr_reader :reminders_file
|
|
177
|
-
|
|
178
|
-
def initialize(reminders_file = REMINDERS_FILE)
|
|
179
|
-
@reminders_file = reminders_file
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def ensure_reminders_file
|
|
183
|
-
dir = File.dirname(@reminders_file)
|
|
184
|
-
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
185
|
-
File.write(@reminders_file, [].to_yaml) unless File.exist?(@reminders_file)
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def load_reminders
|
|
189
|
-
ensure_reminders_file
|
|
190
|
-
data = YAML.load_file(@reminders_file) || []
|
|
191
|
-
data.map { |hash| Reminder.from_hash(hash) }
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def save_reminders(reminders)
|
|
195
|
-
ensure_reminders_file
|
|
196
|
-
data = reminders.map(&:to_h)
|
|
197
|
-
File.write(@reminders_file, data.to_yaml)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def load_reminder_hash(reminders = nil)
|
|
201
|
-
reminders ||= load_reminders
|
|
202
|
-
reminders.each_with_index.each_with_object({}) do |(reminder, idx), h|
|
|
203
|
-
h[reminder.display_string(idx)] = reminder
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def edit_reminders(reminders = nil)
|
|
208
|
-
reminders_array = if reminders.nil?
|
|
209
|
-
[REMINDER_TEMPLATE]
|
|
210
|
-
elsif reminders.is_a?(Array)
|
|
211
|
-
reminders.map { |r| r.is_a?(Reminder) ? r.to_h : r }
|
|
212
|
-
else
|
|
213
|
-
[reminders.is_a?(Reminder) ? reminders.to_h : reminders]
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
tmpfile = Tempfile.new(['reminder-edit-', '.yml'])
|
|
217
|
-
tmpfile.write(reminders_array.to_yaml)
|
|
218
|
-
tmpfile.close
|
|
219
|
-
|
|
220
|
-
system(ENV['EDITOR'] || 'vim', tmpfile.path)
|
|
221
|
-
|
|
222
|
-
updated_data = YAML.load_file(tmpfile.path)
|
|
223
|
-
tmpfile.unlink
|
|
224
|
-
|
|
225
|
-
updated_array = updated_data.is_a?(Array) ? updated_data : [updated_data]
|
|
226
|
-
updated_array.map { |hash| Reminder.from_hash(hash) }
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
def find_reminder_by_ref(ref, reminders)
|
|
230
|
-
if ref =~ /^\d+$/
|
|
231
|
-
idx = ref.to_i - 1
|
|
232
|
-
return [idx, reminders[idx]] if idx >= 0 && idx < reminders.length
|
|
233
|
-
end
|
|
234
|
-
[nil, nil]
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
rm = ReminderManager.new
|
|
239
|
-
o = Hiiro.init(*ARGV, plugins: [Pins], reminders_file: rm.reminders_file)
|
|
240
|
-
|
|
241
|
-
o.add_subcmd(:add) do |message = nil, time_arg = nil, sound = nil, *args|
|
|
242
|
-
reminders = rm.load_reminders
|
|
243
|
-
|
|
244
|
-
if message.nil?
|
|
245
|
-
new_reminders = rm.edit_reminders
|
|
246
|
-
|
|
247
|
-
reminders.concat(new_reminders)
|
|
248
|
-
rm.save_reminders(reminders)
|
|
249
|
-
|
|
250
|
-
if new_reminders.length == 1
|
|
251
|
-
puts "Saved reminder ##{reminders.length}: #{new_reminders.first.message}"
|
|
252
|
-
else
|
|
253
|
-
puts "Saved #{new_reminders.length} reminders (#{reminders.length - new_reminders.length + 1}-#{reminders.length})"
|
|
254
|
-
end
|
|
255
|
-
next
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
is_once, trigger_at, interval = ReminderManager.parse_time_arg(time_arg)
|
|
259
|
-
|
|
260
|
-
new_reminder = ReminderManager::Reminder.new(
|
|
261
|
-
message: message,
|
|
262
|
-
interval: interval,
|
|
263
|
-
sound: sound,
|
|
264
|
-
once: is_once,
|
|
265
|
-
trigger_at: trigger_at,
|
|
266
|
-
created_at: Time.now.iso8601
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
reminders << new_reminder
|
|
270
|
-
rm.save_reminders(reminders)
|
|
271
|
-
|
|
272
|
-
if is_once
|
|
273
|
-
trigger_time = Time.parse(trigger_at)
|
|
274
|
-
puts "Saved one-time reminder ##{reminders.length}: #{message} (at #{trigger_time.strftime('%Y-%m-%d %H:%M')})"
|
|
275
|
-
else
|
|
276
|
-
puts "Saved recurring reminder ##{reminders.length}: #{message} (every #{interval || '1h'})"
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
o.add_subcmd(:once) do |message = nil, time_arg = nil, sound = nil, *args|
|
|
281
|
-
reminders = rm.load_reminders
|
|
282
|
-
|
|
283
|
-
if message.nil?
|
|
284
|
-
puts "Usage: h remind once 'message' <time> [sound]"
|
|
285
|
-
puts " time: 15m, 2h, 1d OR YYYYmmddHHMM"
|
|
286
|
-
next
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
time_arg ||= '1h'
|
|
290
|
-
is_once, trigger_at, interval = ReminderManager.parse_time_arg(time_arg)
|
|
291
|
-
|
|
292
|
-
# Force one-time even if parse didn't detect it
|
|
293
|
-
unless trigger_at
|
|
294
|
-
seconds = ReminderManager::Reminder.new(message: '').parse_interval(time_arg)
|
|
295
|
-
trigger_at = (Time.now + seconds).iso8601
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
new_reminder = ReminderManager::Reminder.new(
|
|
299
|
-
message: message,
|
|
300
|
-
interval: interval,
|
|
301
|
-
sound: sound,
|
|
302
|
-
once: true,
|
|
303
|
-
trigger_at: trigger_at,
|
|
304
|
-
created_at: Time.now.iso8601
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
reminders << new_reminder
|
|
308
|
-
rm.save_reminders(reminders)
|
|
309
|
-
|
|
310
|
-
trigger_time = Time.parse(trigger_at)
|
|
311
|
-
puts "Saved one-time reminder ##{reminders.length}: #{message} (at #{trigger_time.strftime('%Y-%m-%d %H:%M')})"
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
o.add_subcmd(:every) do |message = nil, interval = nil, sound = nil, *args|
|
|
315
|
-
reminders = rm.load_reminders
|
|
316
|
-
|
|
317
|
-
if message.nil?
|
|
318
|
-
puts "Usage: h remind every 'message' <interval> [sound]"
|
|
319
|
-
puts " interval: 15m, 2h, 1d, 1w"
|
|
320
|
-
next
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
new_reminder = ReminderManager::Reminder.new(
|
|
324
|
-
message: message,
|
|
325
|
-
interval: interval || '1h',
|
|
326
|
-
sound: sound,
|
|
327
|
-
once: false,
|
|
328
|
-
created_at: Time.now.iso8601
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
reminders << new_reminder
|
|
332
|
-
rm.save_reminders(reminders)
|
|
333
|
-
|
|
334
|
-
puts "Saved recurring reminder ##{reminders.length}: #{message} (every #{interval || '1h'})"
|
|
335
|
-
end
|
|
336
|
-
|
|
337
|
-
o.add_subcmd(:ls) do |*args|
|
|
338
|
-
reminders = rm.load_reminders
|
|
339
|
-
if reminders.empty?
|
|
340
|
-
puts "No reminders saved."
|
|
341
|
-
else
|
|
342
|
-
reminders.each_with_index do |reminder, idx|
|
|
343
|
-
puts reminder.display_string(idx)
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
o.add_subcmd(:list) do |*args|
|
|
349
|
-
o.run_subcmd(:ls, *args)
|
|
350
|
-
end
|
|
351
|
-
|
|
352
|
-
o.add_subcmd(:remove) do |ref = nil, *args|
|
|
353
|
-
reminders = rm.load_reminders
|
|
354
|
-
|
|
355
|
-
if reminders.empty?
|
|
356
|
-
puts "No reminders to remove."
|
|
357
|
-
next
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
idx = nil
|
|
361
|
-
if ref.nil?
|
|
362
|
-
lines = rm.load_reminder_hash(reminders)
|
|
363
|
-
|
|
364
|
-
require 'open3'
|
|
365
|
-
selected, status = Open3.capture2('sk', stdin_data: lines.keys.join("\n"))
|
|
366
|
-
|
|
367
|
-
if status.success? && !selected.strip.empty?
|
|
368
|
-
reminder = lines[selected.strip]
|
|
369
|
-
idx = reminders.index(reminder)
|
|
370
|
-
end
|
|
371
|
-
else
|
|
372
|
-
idx, _ = rm.find_reminder_by_ref(ref, reminders)
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
if idx.nil?
|
|
376
|
-
puts "Reminder not found"
|
|
377
|
-
next
|
|
378
|
-
end
|
|
379
|
-
|
|
380
|
-
removed = reminders.delete_at(idx)
|
|
381
|
-
rm.save_reminders(reminders)
|
|
382
|
-
puts "Removed reminder: #{removed.message}"
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
o.add_subcmd(:pause) do |ref = nil, *args|
|
|
386
|
-
reminders = rm.load_reminders
|
|
387
|
-
|
|
388
|
-
if reminders.empty?
|
|
389
|
-
puts "No reminders to pause."
|
|
390
|
-
next
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
idx = nil
|
|
394
|
-
if ref.nil?
|
|
395
|
-
enabled_reminders = reminders.select(&:enabled)
|
|
396
|
-
if enabled_reminders.empty?
|
|
397
|
-
puts "No enabled reminders to pause."
|
|
398
|
-
next
|
|
399
|
-
end
|
|
400
|
-
|
|
401
|
-
lines = enabled_reminders.each_with_index.each_with_object({}) do |(r, i), h|
|
|
402
|
-
actual_idx = reminders.index(r)
|
|
403
|
-
h[r.display_string(actual_idx)] = actual_idx
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
require 'open3'
|
|
407
|
-
selected, status = Open3.capture2('sk', stdin_data: lines.keys.join("\n"))
|
|
408
|
-
|
|
409
|
-
if status.success? && !selected.strip.empty?
|
|
410
|
-
idx = lines[selected.strip]
|
|
411
|
-
end
|
|
412
|
-
else
|
|
413
|
-
idx, _ = rm.find_reminder_by_ref(ref, reminders)
|
|
414
|
-
end
|
|
415
|
-
|
|
416
|
-
if idx.nil?
|
|
417
|
-
puts "Reminder not found"
|
|
418
|
-
next
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
reminders[idx].enabled = false
|
|
422
|
-
rm.save_reminders(reminders)
|
|
423
|
-
puts "Paused reminder: #{reminders[idx].message}"
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
o.add_subcmd(:resume) do |ref = nil, *args|
|
|
427
|
-
reminders = rm.load_reminders
|
|
428
|
-
|
|
429
|
-
if reminders.empty?
|
|
430
|
-
puts "No reminders to resume."
|
|
431
|
-
next
|
|
432
|
-
end
|
|
433
|
-
|
|
434
|
-
idx = nil
|
|
435
|
-
if ref.nil?
|
|
436
|
-
disabled_reminders = reminders.reject(&:enabled)
|
|
437
|
-
if disabled_reminders.empty?
|
|
438
|
-
puts "No paused reminders to resume."
|
|
439
|
-
next
|
|
440
|
-
end
|
|
441
|
-
|
|
442
|
-
lines = disabled_reminders.each_with_index.each_with_object({}) do |(r, i), h|
|
|
443
|
-
actual_idx = reminders.index(r)
|
|
444
|
-
h[r.display_string(actual_idx)] = actual_idx
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
require 'open3'
|
|
448
|
-
selected, status = Open3.capture2('sk', stdin_data: lines.keys.join("\n"))
|
|
449
|
-
|
|
450
|
-
if status.success? && !selected.strip.empty?
|
|
451
|
-
idx = lines[selected.strip]
|
|
452
|
-
end
|
|
453
|
-
else
|
|
454
|
-
idx, _ = rm.find_reminder_by_ref(ref, reminders)
|
|
455
|
-
end
|
|
456
|
-
|
|
457
|
-
if idx.nil?
|
|
458
|
-
puts "Reminder not found"
|
|
459
|
-
next
|
|
460
|
-
end
|
|
461
|
-
|
|
462
|
-
reminders[idx].enabled = true
|
|
463
|
-
rm.save_reminders(reminders)
|
|
464
|
-
puts "Resumed reminder: #{reminders[idx].message}"
|
|
465
|
-
end
|
|
466
|
-
|
|
467
|
-
o.add_subcmd(:check) do |*args|
|
|
468
|
-
reminders = rm.load_reminders
|
|
469
|
-
triggered_count = 0
|
|
470
|
-
verbose = args.include?('-v') || args.include?('--verbose')
|
|
471
|
-
|
|
472
|
-
reminders.each do |reminder|
|
|
473
|
-
next unless reminder.should_trigger?
|
|
474
|
-
|
|
475
|
-
# Build notification options
|
|
476
|
-
notification_args = ['-m', reminder.message, '-t', 'Reminder']
|
|
477
|
-
notification_args += ['-s', reminder.sound] if reminder.sound
|
|
478
|
-
|
|
479
|
-
# Use Hiiro::Notification to show the notification
|
|
480
|
-
notif_hiiro = Hiiro.init(*notification_args)
|
|
481
|
-
Hiiro::Notification.show(notif_hiiro)
|
|
482
|
-
|
|
483
|
-
reminder.trigger!
|
|
484
|
-
triggered_count += 1
|
|
485
|
-
|
|
486
|
-
if verbose
|
|
487
|
-
if reminder.one_time?
|
|
488
|
-
puts "Triggered (one-time): #{reminder.message}"
|
|
489
|
-
else
|
|
490
|
-
puts "Triggered: #{reminder.message}"
|
|
491
|
-
end
|
|
492
|
-
end
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
rm.save_reminders(reminders)
|
|
496
|
-
|
|
497
|
-
if verbose
|
|
498
|
-
enabled_count = reminders.count(&:enabled)
|
|
499
|
-
one_time_done = reminders.count { |r| r.one_time? && !r.enabled }
|
|
500
|
-
puts "Checked #{enabled_count} enabled reminders, triggered #{triggered_count}"
|
|
501
|
-
puts "#{one_time_done} completed one-time reminder(s) (use 'h remind clean' to remove)" if one_time_done > 0
|
|
502
|
-
end
|
|
503
|
-
end
|
|
504
|
-
|
|
505
|
-
o.add_subcmd(:clean) do |*args|
|
|
506
|
-
reminders = rm.load_reminders
|
|
507
|
-
before_count = reminders.length
|
|
508
|
-
|
|
509
|
-
# Remove disabled one-time reminders (already triggered)
|
|
510
|
-
reminders.reject! { |r| r.one_time? && !r.enabled }
|
|
511
|
-
|
|
512
|
-
after_count = reminders.length
|
|
513
|
-
removed = before_count - after_count
|
|
514
|
-
|
|
515
|
-
rm.save_reminders(reminders)
|
|
516
|
-
|
|
517
|
-
if removed > 0
|
|
518
|
-
puts "Removed #{removed} completed one-time reminder(s)"
|
|
519
|
-
else
|
|
520
|
-
puts "No completed one-time reminders to clean up"
|
|
521
|
-
end
|
|
522
|
-
end
|
|
523
|
-
|
|
524
|
-
o.add_subcmd(:howto) do |*args|
|
|
525
|
-
puts <<~HOWTO
|
|
526
|
-
h remind - Reminder notification system
|
|
527
|
-
|
|
528
|
-
SETUP FOR CRON:
|
|
529
|
-
===============
|
|
530
|
-
Add the following line to your crontab (run 'crontab -e'):
|
|
531
|
-
|
|
532
|
-
* * * * * #{File.expand_path('~/.rbenv/shims/h')} remind check
|
|
533
|
-
|
|
534
|
-
Or if you want verbose logging:
|
|
535
|
-
|
|
536
|
-
* * * * * #{File.expand_path('~/.rbenv/shims/h')} remind check -v >> ~/.config/hiiro/remind.log 2>&1
|
|
537
|
-
|
|
538
|
-
USAGE:
|
|
539
|
-
======
|
|
540
|
-
# One-time reminders (triggers once, then disables)
|
|
541
|
-
h remind add 'Read the news' 15m # Remind in 15 minutes
|
|
542
|
-
h remind add 'Meeting' 202602131430 # Remind at specific time (YYYYmmddHHMM)
|
|
543
|
-
h remind once 'Call mom' 2h # Explicit one-time in 2 hours
|
|
544
|
-
|
|
545
|
-
# Recurring reminders
|
|
546
|
-
h remind every 'Take a break' 30m # Remind every 30 minutes
|
|
547
|
-
h remind every 'Stand up' 1h basso # Remind hourly with 'basso' sound
|
|
548
|
-
|
|
549
|
-
# Editor mode
|
|
550
|
-
h remind add # Open editor to add reminder(s)
|
|
551
|
-
|
|
552
|
-
# Management
|
|
553
|
-
h remind ls # List all reminders
|
|
554
|
-
h remind remove # Select and remove a reminder
|
|
555
|
-
h remind remove 2 # Remove reminder #2
|
|
556
|
-
h remind clean # Remove completed one-time reminders
|
|
557
|
-
|
|
558
|
-
h remind pause # Select and pause a reminder
|
|
559
|
-
h remind pause 1 # Pause reminder #1
|
|
560
|
-
h remind resume # Select and resume a reminder
|
|
561
|
-
h remind resume 1 # Resume reminder #1
|
|
562
|
-
|
|
563
|
-
h remind check # Check and trigger due reminders
|
|
564
|
-
h remind check -v # Verbose mode
|
|
565
|
-
|
|
566
|
-
TIME FORMATS:
|
|
567
|
-
=============
|
|
568
|
-
Relative (one-time): 15m, 2h, 1d - triggers once after delay
|
|
569
|
-
Absolute (one-time): YYYYmmddHHMM - triggers at specific date/time
|
|
570
|
-
e.g., 202602131430 = Feb 13, 2026 at 14:30
|
|
571
|
-
|
|
572
|
-
Use 'h remind every' to create recurring reminders with intervals.
|
|
573
|
-
|
|
574
|
-
INTERVAL FORMAT (for recurring):
|
|
575
|
-
=================================
|
|
576
|
-
s = seconds (e.g., 30s)
|
|
577
|
-
m = minutes (e.g., 10m)
|
|
578
|
-
h = hours (e.g., 2h)
|
|
579
|
-
d = days (e.g., 1d)
|
|
580
|
-
w = weeks (e.g., 1w)
|
|
581
|
-
|
|
582
|
-
SOUNDS:
|
|
583
|
-
=======
|
|
584
|
-
Custom sounds can be placed in: ~/.config/hiiro/sounds/
|
|
585
|
-
Use the filename (without extension) as the sound name.
|
|
586
|
-
|
|
587
|
-
CONFIG FILE:
|
|
588
|
-
============
|
|
589
|
-
Reminders are stored in: #{ReminderManager::REMINDERS_FILE}
|
|
590
|
-
HOWTO
|
|
591
|
-
end
|
|
592
|
-
|
|
593
|
-
o.add_subcmd(:path) do |*args|
|
|
594
|
-
print rm.reminders_file
|
|
595
|
-
end
|
|
596
|
-
|
|
597
|
-
o.add_subcmd(:editall) do |*args|
|
|
598
|
-
reminders_before = rm.load_reminders
|
|
599
|
-
system(ENV['EDITOR'] || 'vim', rm.reminders_file)
|
|
600
|
-
|
|
601
|
-
begin
|
|
602
|
-
rm.load_reminders
|
|
603
|
-
rescue => e
|
|
604
|
-
puts "ERROR: Unable to read updated file...reverting."
|
|
605
|
-
rm.save_reminders(reminders_before)
|
|
606
|
-
end
|
|
607
|
-
end
|
|
608
|
-
|
|
609
|
-
begin
|
|
610
|
-
o.run
|
|
611
|
-
rescue => e
|
|
612
|
-
require 'pry'
|
|
613
|
-
binding.pry
|
|
614
|
-
end
|