flycal-cli 0.6.1 → 0.7.1
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/README.md +53 -33
- data/config/defaults.yml +23 -5
- data/lib/flycal_cli/cli.rb +41 -9
- data/lib/flycal_cli/config.rb +36 -1
- data/lib/flycal_cli/slot_finder.rb +13 -14
- data/lib/flycal_cli/slot_formatter.rb +12 -7
- data/lib/flycal_cli/version.rb +1 -1
- data/locales/en.json +1 -1
- data/locales/it.json +1 -1
- data/mcp/tools.json +15 -14
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91347372adf44296db16d6e06a90a327a2b1dd3709137dc207dc4eadaab3a472
|
|
4
|
+
data.tar.gz: 0ec160527a68a2dee05f466626c28ee8bedd750a05928978a1f77991ae3be779
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b8eaa8bdc8e56fbb6f865d77811e017ab239bd800b8e507bbe3477a4513663335dc74ca9f28b3111eeff38fe37cc5aa010dea81d4339074bd38e9ec2c7cf7f0
|
|
7
|
+
data.tar.gz: faf3a5c3bad9ebf2b966b9ca66f1b924d619496cde86b4c8d1b3246a8af2488a12dc6a5b23cbf2f7be147e1cc09e96ed99b6aa23d7d74ab6e5ce9f2e97ed69b3
|
data/README.md
CHANGED
|
@@ -157,10 +157,10 @@ Find free time slots in your calendar. Output is a simple list for copy/paste in
|
|
|
157
157
|
|
|
158
158
|
```bash
|
|
159
159
|
flycal slots
|
|
160
|
+
flycal slots --in "5 days"
|
|
161
|
+
flycal slots --in "12 days" --template dinner
|
|
160
162
|
flycal slots --duration 1h
|
|
161
163
|
flycal slots --from 2026-08-01 --in "2 weeks" --duration 1h
|
|
162
|
-
flycal slots --from 01/08/2026 --in 3days --duration 30min
|
|
163
|
-
flycal slots --in 1week -c Work
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
**Options:**
|
|
@@ -168,24 +168,26 @@ flycal slots --in 1week -c Work
|
|
|
168
168
|
- `--duration` — Slot length. Default: `slots.defaults.default_duration` in config (`45min`). Examples: `1h`, `30 minutes`
|
|
169
169
|
- `--from` / `-f` — Start date/time. Default: `slots.defaults.from` in config (`now`). If a date without time is today, starts from the current time.
|
|
170
170
|
- `--in` / `-i` — Search window from `--from`. Default: `1 week`.
|
|
171
|
+
- `--template` / `-T` — Template from `slots.templates` in config. Default: first template (`work`)
|
|
171
172
|
- `--calendar` / `-c` — Calendar name or ID used as fallback when `exclude_calendars` is not configured
|
|
172
173
|
|
|
173
174
|
**Output:**
|
|
174
175
|
|
|
175
|
-
Always starts with a header and clickable calendar links (OSC 8), then the availability list:
|
|
176
|
-
|
|
177
176
|
```
|
|
178
|
-
found
|
|
179
|
-
from
|
|
180
|
-
with duration 45min
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
found 6 slots
|
|
178
|
+
from sat 1 August 2026 to sat 8 August 2026
|
|
179
|
+
with duration 45min, template dinner
|
|
180
|
+
considering calendars
|
|
181
|
+
- incode - antonio
|
|
182
|
+
- Polimi 10110009
|
|
183
|
+
- antoniomolinari1977@gmail.com
|
|
184
|
+
link: https://calendar.google.com/calendar/r/day/2026/8/1
|
|
185
|
+
|
|
186
|
+
saturday 1/8
|
|
187
|
+
19 - 23
|
|
188
|
+
|
|
189
|
+
monday 3/8
|
|
190
|
+
19 - 23
|
|
189
191
|
```
|
|
190
192
|
|
|
191
193
|
If `pbcopy` is available (macOS), the slot list without the header is also copied to the clipboard.
|
|
@@ -193,44 +195,62 @@ If `pbcopy` is available (macOS), the slot list without the header is also copie
|
|
|
193
195
|
Slot search windows are configured in `~/.flycal/config.yml`:
|
|
194
196
|
|
|
195
197
|
```yaml
|
|
198
|
+
calendar_default: ~
|
|
199
|
+
locale: en
|
|
196
200
|
slots:
|
|
201
|
+
templates:
|
|
202
|
+
work:
|
|
203
|
+
days:
|
|
204
|
+
- 1
|
|
205
|
+
- 2
|
|
206
|
+
- 3
|
|
207
|
+
- 4
|
|
208
|
+
- 5
|
|
209
|
+
hours:
|
|
210
|
+
- 9:30-13:00
|
|
211
|
+
- 14:00-18:30
|
|
212
|
+
dinner:
|
|
213
|
+
days:
|
|
214
|
+
- 1
|
|
215
|
+
- 2
|
|
216
|
+
- 3
|
|
217
|
+
- 4
|
|
218
|
+
- 5
|
|
219
|
+
- 6
|
|
220
|
+
- 7
|
|
221
|
+
hours:
|
|
222
|
+
- 19-23
|
|
223
|
+
exclude_calendars: []
|
|
197
224
|
defaults:
|
|
198
225
|
from: now
|
|
199
226
|
default_duration: 45min
|
|
200
227
|
free_before: 0m
|
|
201
228
|
free_after: 15m
|
|
202
|
-
exclude_calendars:
|
|
203
|
-
- user@example.com
|
|
204
|
-
- user@gmail.com
|
|
205
|
-
hours:
|
|
206
|
-
- 9:30-13:00
|
|
207
|
-
- 14:00-18:30
|
|
208
|
-
weekdays_only: true
|
|
209
|
-
locale: en
|
|
210
229
|
```
|
|
211
230
|
|
|
212
231
|
Missing keys are filled from `config/defaults.yml` in the gem and saved to your `config.yml` on first read.
|
|
213
232
|
|
|
233
|
+
- `calendar_default: ~` means no default calendar is set (`~` is YAML null)
|
|
234
|
+
- `templates` — named schedules; default used is the first one (`work`)
|
|
235
|
+
- `templates.*.days` — weekdays as numbers: `1` Monday … `7` Sunday
|
|
236
|
+
- `templates.*.hours` — one or more ranges (`H-H`, `HH:MM-HH:MM`, mixed)
|
|
214
237
|
- `defaults.from` — default `--from` for slots (`now` or a date string)
|
|
215
238
|
- `defaults.default_duration` — default slot length when `--duration` is omitted
|
|
216
|
-
- `free_before` — buffer before each slot
|
|
217
|
-
- `free_after` — buffer after each slot; gaps must fit `duration + free_after`,
|
|
218
|
-
- `exclude_calendars`
|
|
219
|
-
- `hours` accepts one or more ranges (`H-H`, `HH:MM-HH:MM`, mixed)
|
|
220
|
-
- `weekdays_only: true` limits slots to Monday-Friday
|
|
221
|
-
- `weekdays_only: false` includes weekends
|
|
239
|
+
- `free_before` — buffer before each slot
|
|
240
|
+
- `free_after` — buffer after each slot; gaps must fit `duration + free_after`, output shows continuous free ranges
|
|
241
|
+
- `exclude_calendars` — calendars whose events block free slots; if empty, `calendar_default` is used
|
|
222
242
|
- `locale` supports `en` and `it` (default is `en`)
|
|
223
243
|
|
|
224
244
|
**Output example:**
|
|
225
245
|
|
|
226
246
|
```
|
|
227
247
|
friday 15/7
|
|
228
|
-
10-12
|
|
229
|
-
13-15.30
|
|
248
|
+
10 - 12
|
|
249
|
+
13 - 15.30
|
|
230
250
|
|
|
231
251
|
monday 21/7
|
|
232
|
-
12-13
|
|
233
|
-
14-15
|
|
252
|
+
12 - 13
|
|
253
|
+
14 - 15
|
|
234
254
|
```
|
|
235
255
|
|
|
236
256
|
## Configuration
|
data/config/defaults.yml
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
|
+
calendar_default: ~
|
|
1
2
|
locale: en
|
|
2
3
|
slots:
|
|
4
|
+
templates:
|
|
5
|
+
work:
|
|
6
|
+
days:
|
|
7
|
+
- 1
|
|
8
|
+
- 2
|
|
9
|
+
- 3
|
|
10
|
+
- 4
|
|
11
|
+
- 5
|
|
12
|
+
hours:
|
|
13
|
+
- 9:30-13:00
|
|
14
|
+
- 14:00-18:30
|
|
15
|
+
dinner:
|
|
16
|
+
days:
|
|
17
|
+
- 1
|
|
18
|
+
- 2
|
|
19
|
+
- 3
|
|
20
|
+
- 4
|
|
21
|
+
- 5
|
|
22
|
+
- 6
|
|
23
|
+
hours:
|
|
24
|
+
- 19-23
|
|
25
|
+
exclude_calendars: []
|
|
3
26
|
defaults:
|
|
4
27
|
from: now
|
|
5
28
|
default_duration: 45min
|
|
6
29
|
free_before: 0m
|
|
7
30
|
free_after: 15m
|
|
8
|
-
exclude_calendars: []
|
|
9
|
-
hours:
|
|
10
|
-
- 9:30-13:00
|
|
11
|
-
- 14:00-18:30
|
|
12
|
-
weekdays_only: true
|
data/lib/flycal_cli/cli.rb
CHANGED
|
@@ -182,11 +182,13 @@ module FlycalCli
|
|
|
182
182
|
--from from config (default: now)
|
|
183
183
|
--in 1 week
|
|
184
184
|
--duration from config (default: 45min)
|
|
185
|
+
--template first template in config (default: work)
|
|
185
186
|
|
|
186
187
|
Examples:
|
|
187
188
|
flycal slots
|
|
188
|
-
flycal slots --
|
|
189
|
-
flycal slots --
|
|
189
|
+
flycal slots --in "5 days"
|
|
190
|
+
flycal slots --in "12 days" --template dinner
|
|
191
|
+
flycal slots --duration 1h --from 2026-08-01
|
|
190
192
|
LONGDESC
|
|
191
193
|
option :duration, type: :string,
|
|
192
194
|
desc: "Slot length (default from config: slots.defaults.default_duration)"
|
|
@@ -194,6 +196,8 @@ module FlycalCli
|
|
|
194
196
|
desc: "Search window from --from (default: 1 week)"
|
|
195
197
|
option :from, type: :string, aliases: "-f",
|
|
196
198
|
desc: "Start date/time (default from config: slots.defaults.from)"
|
|
199
|
+
option :template, type: :string, aliases: "-T",
|
|
200
|
+
desc: "Slot template name from config (default: first template, usually work)"
|
|
197
201
|
option :calendar, type: :string, aliases: "-c", desc: "Calendar name or ID"
|
|
198
202
|
def slots
|
|
199
203
|
apply_locale_override
|
|
@@ -216,13 +220,14 @@ module FlycalCli
|
|
|
216
220
|
from_value = "now" if from_value.empty?
|
|
217
221
|
|
|
218
222
|
begin
|
|
223
|
+
template_name, template = resolve_slots_template(slot_cfg, options[:template])
|
|
219
224
|
slot_duration = DurationParser.to_seconds(duration_value)
|
|
220
225
|
free_before = DurationParser.to_seconds(slot_cfg["free_before"] || "0m")
|
|
221
226
|
free_after = DurationParser.to_seconds(slot_cfg["free_after"] || "0m")
|
|
222
227
|
time_min = parse_slots_from(from_value)
|
|
223
228
|
time_max = DurationParser.add_to_time(in_value, time_min)
|
|
224
|
-
|
|
225
|
-
|
|
229
|
+
hours = parse_workhours(template["hours"])
|
|
230
|
+
days = parse_template_days(template["days"])
|
|
226
231
|
rescue FlycalCli::Error => e
|
|
227
232
|
puts "Error: #{e.message}"
|
|
228
233
|
exit 1
|
|
@@ -266,8 +271,8 @@ module FlycalCli
|
|
|
266
271
|
time_min: time_min,
|
|
267
272
|
time_max: time_max,
|
|
268
273
|
slot_duration_seconds: slot_duration,
|
|
269
|
-
|
|
270
|
-
|
|
274
|
+
hours: hours,
|
|
275
|
+
days: days,
|
|
271
276
|
free_before_seconds: free_before,
|
|
272
277
|
free_after_seconds: free_after
|
|
273
278
|
)
|
|
@@ -279,7 +284,8 @@ module FlycalCli
|
|
|
279
284
|
to: time_max,
|
|
280
285
|
duration: duration_value,
|
|
281
286
|
calendars: calendar_meta,
|
|
282
|
-
count: slot_count
|
|
287
|
+
count: slot_count,
|
|
288
|
+
template: template_name
|
|
283
289
|
)
|
|
284
290
|
puts ""
|
|
285
291
|
output = SlotFormatter.format_output(slots_by_day)
|
|
@@ -472,10 +478,36 @@ module FlycalCli
|
|
|
472
478
|
DateTimeParser.parse(value)
|
|
473
479
|
end
|
|
474
480
|
|
|
481
|
+
def resolve_slots_template(slot_cfg, requested_name)
|
|
482
|
+
templates = slot_cfg["templates"]
|
|
483
|
+
raise FlycalCli::Error, "slots.templates is missing in config.yml." unless templates.is_a?(Hash) && !templates.empty?
|
|
484
|
+
|
|
485
|
+
name = requested_name.to_s.strip
|
|
486
|
+
name = templates.keys.first.to_s if name.empty?
|
|
487
|
+
|
|
488
|
+
template = templates[name]
|
|
489
|
+
unless template.is_a?(Hash)
|
|
490
|
+
available = templates.keys.join(", ")
|
|
491
|
+
raise FlycalCli::Error, "Unknown slots template #{name.inspect}. Available: #{available}"
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
[name, template]
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def parse_template_days(values)
|
|
498
|
+
days = Array(values).map(&:to_i)
|
|
499
|
+
raise FlycalCli::Error, "slots template days cannot be empty." if days.empty?
|
|
500
|
+
|
|
501
|
+
invalid = days.reject { |d| d.between?(1, 7) }
|
|
502
|
+
raise FlycalCli::Error, "Invalid template days #{invalid.inspect}. Use 1 (Mon) .. 7 (Sun)." unless invalid.empty?
|
|
503
|
+
|
|
504
|
+
days.uniq
|
|
505
|
+
end
|
|
506
|
+
|
|
475
507
|
def copy_slots_to_clipboard(text)
|
|
476
508
|
return unless pbcopy_available?
|
|
477
509
|
|
|
478
|
-
IO.popen("pbcopy", "w") { |io| io.write(text) }
|
|
510
|
+
IO.popen("pbcopy", "w") { |io| io.write(SlotFormatter.strip_ansi(text)) }
|
|
479
511
|
puts ""
|
|
480
512
|
puts Locale.t("slots.copied_to_clipboard")
|
|
481
513
|
rescue StandardError
|
|
@@ -513,7 +545,7 @@ module FlycalCli
|
|
|
513
545
|
[sh, sm, eh, em]
|
|
514
546
|
end
|
|
515
547
|
|
|
516
|
-
raise FlycalCli::Error, "
|
|
548
|
+
raise FlycalCli::Error, "template hours cannot be empty in config.yml." if ranges.empty?
|
|
517
549
|
|
|
518
550
|
ranges.sort_by { |sh, sm, _eh, _em| (sh * 60) + sm }
|
|
519
551
|
end
|
data/lib/flycal_cli/config.rb
CHANGED
|
@@ -32,7 +32,10 @@ module FlycalCli
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def calendar_default
|
|
35
|
-
load["calendar_default"]
|
|
35
|
+
value = load["calendar_default"]
|
|
36
|
+
return nil if value.nil? || value.to_s.strip.empty? || value.to_s == "~"
|
|
37
|
+
|
|
38
|
+
value.to_s
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
def calendar_default=(calendar_id)
|
|
@@ -98,6 +101,38 @@ module FlycalCli
|
|
|
98
101
|
changed = true
|
|
99
102
|
end
|
|
100
103
|
|
|
104
|
+
if normalized.key?("tempaltes") && !normalized.key?("templates")
|
|
105
|
+
normalized["templates"] = normalized.delete("tempaltes")
|
|
106
|
+
changed = true
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
templates = normalized["templates"]
|
|
110
|
+
templates_empty = !templates.is_a?(Hash) || templates.empty?
|
|
111
|
+
|
|
112
|
+
if templates_empty && normalized["hours"]
|
|
113
|
+
days =
|
|
114
|
+
if normalized.key?("weekdays_only") && normalized["weekdays_only"] == false
|
|
115
|
+
[1, 2, 3, 4, 5, 6, 7]
|
|
116
|
+
else
|
|
117
|
+
[1, 2, 3, 4, 5]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
normalized["templates"] = {
|
|
121
|
+
"work" => {
|
|
122
|
+
"days" => days,
|
|
123
|
+
"hours" => deep_dup(normalized["hours"])
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
changed = true
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
%w[hours weekdays_only weekdays-only workhours tempaltes].each do |legacy_key|
|
|
130
|
+
next unless normalized.key?(legacy_key)
|
|
131
|
+
|
|
132
|
+
normalized.delete(legacy_key)
|
|
133
|
+
changed = true
|
|
134
|
+
end
|
|
135
|
+
|
|
101
136
|
return [data, false] unless changed
|
|
102
137
|
|
|
103
138
|
data = data.dup
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module FlycalCli
|
|
4
4
|
class SlotFinder
|
|
5
|
-
|
|
5
|
+
DEFAULT_HOURS = [[9, 0, 18, 0]].freeze
|
|
6
|
+
DEFAULT_DAYS = [1, 2, 3, 4, 5].freeze
|
|
6
7
|
STEP_SECONDS = 900
|
|
7
8
|
|
|
8
9
|
def initialize(
|
|
@@ -10,8 +11,8 @@ module FlycalCli
|
|
|
10
11
|
time_min:,
|
|
11
12
|
time_max:,
|
|
12
13
|
slot_duration_seconds:,
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
hours: DEFAULT_HOURS,
|
|
15
|
+
days: DEFAULT_DAYS,
|
|
15
16
|
free_before_seconds: 0,
|
|
16
17
|
free_after_seconds: 0
|
|
17
18
|
)
|
|
@@ -19,8 +20,8 @@ module FlycalCli
|
|
|
19
20
|
@time_min = time_min
|
|
20
21
|
@time_max = time_max
|
|
21
22
|
@slot_duration_seconds = slot_duration_seconds
|
|
22
|
-
@
|
|
23
|
-
@
|
|
23
|
+
@hours = hours
|
|
24
|
+
@days = Array(days).map(&:to_i)
|
|
24
25
|
@free_before_seconds = free_before_seconds
|
|
25
26
|
@free_after_seconds = free_after_seconds
|
|
26
27
|
end
|
|
@@ -28,7 +29,7 @@ module FlycalCli
|
|
|
28
29
|
def slots_by_day
|
|
29
30
|
result = {}
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
each_template_day do |date|
|
|
32
33
|
slots = []
|
|
33
34
|
day_intervals(date).each do |day_start, day_end|
|
|
34
35
|
next if day_end <= day_start
|
|
@@ -47,7 +48,7 @@ module FlycalCli
|
|
|
47
48
|
|
|
48
49
|
private
|
|
49
50
|
|
|
50
|
-
def
|
|
51
|
+
def each_template_day
|
|
51
52
|
date = @time_min.to_date
|
|
52
53
|
end_date = @time_max.to_date
|
|
53
54
|
|
|
@@ -57,18 +58,17 @@ module FlycalCli
|
|
|
57
58
|
end
|
|
58
59
|
end
|
|
59
60
|
|
|
61
|
+
# Template days: 1=Monday ... 7=Sunday
|
|
60
62
|
def include_day?(date)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
workday?(date)
|
|
63
|
+
@days.include?(iso_weekday(date))
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
def
|
|
67
|
-
|
|
66
|
+
def iso_weekday(date)
|
|
67
|
+
date.wday.zero? ? 7 : date.wday
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def day_intervals(date)
|
|
71
|
-
@
|
|
71
|
+
@hours.map do |start_h, start_m, end_h, end_m|
|
|
72
72
|
start_at = Time.local(date.year, date.month, date.day, start_h, start_m, 0)
|
|
73
73
|
end_at = Time.local(date.year, date.month, date.day, end_h, end_m, 0)
|
|
74
74
|
start_at = [@time_min, start_at].max
|
|
@@ -147,7 +147,6 @@ module FlycalCli
|
|
|
147
147
|
return [] if rounded_start >= rounded_end
|
|
148
148
|
return [] if (rounded_end - rounded_start) < @slot_duration_seconds
|
|
149
149
|
|
|
150
|
-
# One continuous free range (not sliding windows of fixed duration)
|
|
151
150
|
[[rounded_start, rounded_end]]
|
|
152
151
|
end
|
|
153
152
|
|
|
@@ -3,19 +3,20 @@
|
|
|
3
3
|
module FlycalCli
|
|
4
4
|
class SlotFormatter
|
|
5
5
|
class << self
|
|
6
|
-
def format_header(from:, to:, duration:, calendars:, count:)
|
|
6
|
+
def format_header(from:, to:, duration:, calendars:, count:, template: nil)
|
|
7
7
|
lines = []
|
|
8
8
|
lines << Locale.t(
|
|
9
9
|
"slots.header",
|
|
10
|
-
count: count,
|
|
10
|
+
count: underline(count),
|
|
11
11
|
from: underline(Locale.format_long_date(from)),
|
|
12
12
|
to: underline(Locale.format_long_date(to)),
|
|
13
|
-
duration: underline(duration)
|
|
13
|
+
duration: underline(duration),
|
|
14
|
+
template: underline(template.to_s)
|
|
14
15
|
)
|
|
15
16
|
calendars.each do |cal|
|
|
16
|
-
lines << cal[:name]
|
|
17
|
+
lines << "- #{cal[:name]}"
|
|
17
18
|
end
|
|
18
|
-
lines << google_calendar_day_url(from)
|
|
19
|
+
lines << "link: #{google_calendar_day_url(from)}"
|
|
19
20
|
lines.join("\n")
|
|
20
21
|
end
|
|
21
22
|
|
|
@@ -24,7 +25,7 @@ module FlycalCli
|
|
|
24
25
|
|
|
25
26
|
slots_by_day.sort_by(&:first).each do |date, slots|
|
|
26
27
|
lines << "" unless lines.empty?
|
|
27
|
-
lines << day_header(date)
|
|
28
|
+
lines << underline(day_header(date))
|
|
28
29
|
slots.each do |start_at, end_at|
|
|
29
30
|
lines << slot_range(start_at, end_at)
|
|
30
31
|
end
|
|
@@ -33,6 +34,10 @@ module FlycalCli
|
|
|
33
34
|
lines.join("\n")
|
|
34
35
|
end
|
|
35
36
|
|
|
37
|
+
def strip_ansi(text)
|
|
38
|
+
text.to_s.gsub(/\e\[[0-9;]*m/, "")
|
|
39
|
+
end
|
|
40
|
+
|
|
36
41
|
private
|
|
37
42
|
|
|
38
43
|
def underline(str)
|
|
@@ -49,7 +54,7 @@ module FlycalCli
|
|
|
49
54
|
end
|
|
50
55
|
|
|
51
56
|
def slot_range(start_at, end_at)
|
|
52
|
-
"#{format_time(start_at)}
|
|
57
|
+
"#{format_time(start_at)} - #{format_time(end_at)}"
|
|
53
58
|
end
|
|
54
59
|
|
|
55
60
|
def format_time(time)
|
data/lib/flycal_cli/version.rb
CHANGED
data/locales/en.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"slots": {
|
|
32
32
|
"no_available": "No available slots found.",
|
|
33
33
|
"no_calendar": "No calendar found. Run 'flycal config' to set a default.",
|
|
34
|
-
"header": "found %{count} slots\nfrom %{from} to %{to}\nwith duration %{duration}\
|
|
34
|
+
"header": "found %{count} slots\nfrom %{from} to %{to}\nwith duration %{duration}, template %{template}\nconsidering calendars",
|
|
35
35
|
"copied_to_clipboard": "✓ Slot list copied to clipboard (pbcopy)"
|
|
36
36
|
},
|
|
37
37
|
"errors": {
|
data/locales/it.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"slots": {
|
|
32
32
|
"no_available": "Nessuno slot disponibile.",
|
|
33
33
|
"no_calendar": "Nessun calendario trovato. Esegui 'flycal config' per impostare il predefinito.",
|
|
34
|
-
"header": "trovati %{count} slot\nda %{from} a %{to}\ncon durata %{duration}\
|
|
34
|
+
"header": "trovati %{count} slot\nda %{from} a %{to}\ncon durata %{duration}, template %{template}\nconsiderando i calendari",
|
|
35
35
|
"copied_to_clipboard": "✓ Lista slot copiata negli appunti (pbcopy)"
|
|
36
36
|
},
|
|
37
37
|
"errors": {
|
data/mcp/tools.json
CHANGED
|
@@ -17,18 +17,17 @@
|
|
|
17
17
|
"credentials_path": "~/.flycal/credentials.json",
|
|
18
18
|
"keys": {
|
|
19
19
|
"calendar_default": {
|
|
20
|
-
"type": "string",
|
|
21
|
-
"description": "Default Google Calendar ID used by search and as slots fallback"
|
|
20
|
+
"type": ["string", "null"],
|
|
21
|
+
"description": "Default Google Calendar ID used by search and as slots fallback. YAML null (~) means unset."
|
|
22
22
|
},
|
|
23
23
|
"locale": {
|
|
24
24
|
"type": "string",
|
|
25
25
|
"enum": ["en", "it"],
|
|
26
26
|
"description": "Default CLI locale"
|
|
27
27
|
},
|
|
28
|
-
"slots.
|
|
29
|
-
"type": "
|
|
30
|
-
"
|
|
31
|
-
"description": "Slot search hour ranges, e.g. [\"9:30-13:00\", \"14:00-18:30\"]"
|
|
28
|
+
"slots.templates": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"description": "Named templates with days (1=Mon..7=Sun) and hours ranges. Default template is the first key (work)."
|
|
32
31
|
},
|
|
33
32
|
"slots.defaults.from": {
|
|
34
33
|
"type": "string",
|
|
@@ -46,10 +45,6 @@
|
|
|
46
45
|
"type": "string",
|
|
47
46
|
"description": "Buffer after each slot; gaps must fit duration + free_after"
|
|
48
47
|
},
|
|
49
|
-
"slots.weekdays_only": {
|
|
50
|
-
"type": "boolean",
|
|
51
|
-
"description": "When true, slots are searched Mon–Fri only"
|
|
52
|
-
},
|
|
53
48
|
"slots.exclude_calendars": {
|
|
54
49
|
"type": "array",
|
|
55
50
|
"items": { "type": "string" },
|
|
@@ -256,7 +251,7 @@
|
|
|
256
251
|
{
|
|
257
252
|
"name": "flycal_slots",
|
|
258
253
|
"title": "Find free time slots",
|
|
259
|
-
"description": "Find free slots
|
|
254
|
+
"description": "Find free slots using a config template (days + hours). Default template is the first one (work). Use --template dinner for evening hours. Subtracts busy events from slots.exclude_calendars. Starts from --from (default: now) for --in (default: 1 week). Duration defaults to 45min. Respects free_before/free_after. Output: header, template name, calendars, Google Calendar day link, continuous free ranges.",
|
|
260
255
|
"interactive": false,
|
|
261
256
|
"requires_auth": true,
|
|
262
257
|
"command": {
|
|
@@ -266,18 +261,20 @@
|
|
|
266
261
|
"duration": ["--duration", "{value}"],
|
|
267
262
|
"from": ["--from", "{value}"],
|
|
268
263
|
"in": ["--in", "{value}"],
|
|
264
|
+
"template": ["--template", "{value}"],
|
|
269
265
|
"calendar": ["--calendar", "{value}"],
|
|
270
266
|
"locale": ["--locale", "{value}"]
|
|
271
267
|
},
|
|
272
268
|
"examples": [
|
|
273
269
|
["flycal", "slots"],
|
|
274
|
-
["flycal", "slots", "--
|
|
275
|
-
["flycal", "slots", "--
|
|
270
|
+
["flycal", "slots", "--in", "5 days"],
|
|
271
|
+
["flycal", "slots", "--in", "12 days", "--template", "dinner"],
|
|
272
|
+
["flycal", "slots", "--duration", "1h"]
|
|
276
273
|
]
|
|
277
274
|
},
|
|
278
275
|
"output": {
|
|
279
276
|
"format": "text/plain",
|
|
280
|
-
"example": "found
|
|
277
|
+
"example": "found 6 slots\nfrom sat 1 August 2026 to sat 8 August 2026\nwith duration 45min, template dinner\nconsidering calendars\n- Work\nlink: https://calendar.google.com/calendar/r/day/2026/8/1\n\nsaturday 1/8\n19 - 23"
|
|
281
278
|
},
|
|
282
279
|
"inputSchema": {
|
|
283
280
|
"type": "object",
|
|
@@ -296,6 +293,10 @@
|
|
|
296
293
|
"type": "string",
|
|
297
294
|
"description": "Search window from --from. Default: 1 week. Examples: 3 days, 1 week, 48 hours (quote if spaces)."
|
|
298
295
|
},
|
|
296
|
+
"template": {
|
|
297
|
+
"type": "string",
|
|
298
|
+
"description": "Template name from slots.templates (default: first template, usually work)."
|
|
299
|
+
},
|
|
299
300
|
"calendar": {
|
|
300
301
|
"type": "string",
|
|
301
302
|
"description": "Fallback calendar name/ID when exclude_calendars is not configured."
|