flycal-cli 0.4.1 → 0.6.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 +4 -4
- data/README.md +76 -17
- data/config/defaults.yml +8 -2
- data/lib/flycal_cli/cli.rb +270 -102
- data/lib/flycal_cli/config.rb +46 -1
- data/lib/flycal_cli/date_time_parser.rb +129 -0
- data/lib/flycal_cli/duration_parser.rb +15 -6
- data/lib/flycal_cli/locale.rb +6 -0
- data/lib/flycal_cli/slot_finder.rb +30 -12
- data/lib/flycal_cli/slot_formatter.rb +25 -0
- data/lib/flycal_cli/version.rb +1 -1
- data/lib/flycal_cli.rb +1 -0
- data/locales/en.json +26 -2
- data/locales/it.json +26 -2
- data/mcp/README.md +29 -0
- data/mcp/tools.json +318 -0
- metadata +4 -1
data/lib/flycal_cli/config.rb
CHANGED
|
@@ -20,7 +20,8 @@ module FlycalCli
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
merged, changed = merge_missing_defaults(user_data, default_values)
|
|
23
|
-
|
|
23
|
+
merged, migrated = normalize_slots_keys(merged)
|
|
24
|
+
save(merged) if changed || migrated || !File.exist?(CONFIG_FILE)
|
|
24
25
|
|
|
25
26
|
merged
|
|
26
27
|
end
|
|
@@ -64,6 +65,50 @@ module FlycalCli
|
|
|
64
65
|
load.fetch("slots", {})
|
|
65
66
|
end
|
|
66
67
|
|
|
68
|
+
def exclude_calendars
|
|
69
|
+
Array(slots_config["exclude_calendars"]).map(&:to_s).reject(&:empty?)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def exclude_calendars=(calendar_ids)
|
|
73
|
+
data = load
|
|
74
|
+
data["slots"] ||= {}
|
|
75
|
+
data["slots"]["exclude_calendars"] = Array(calendar_ids).map(&:to_s)
|
|
76
|
+
save(data)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def normalize_slots_keys(data)
|
|
80
|
+
slots = data["slots"]
|
|
81
|
+
return [data, false] unless slots.is_a?(Hash)
|
|
82
|
+
|
|
83
|
+
changed = false
|
|
84
|
+
normalized = slots.dup
|
|
85
|
+
|
|
86
|
+
if normalized.key?("exclude-calendars") && !normalized.key?("exclude_calendars")
|
|
87
|
+
normalized["exclude_calendars"] = normalized.delete("exclude-calendars")
|
|
88
|
+
changed = true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if normalized.key?("weekdays-only") && !normalized.key?("weekdays_only")
|
|
92
|
+
normalized["weekdays_only"] = normalized.delete("weekdays-only")
|
|
93
|
+
changed = true
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if normalized.key?("workhours") && !normalized.key?("hours")
|
|
97
|
+
normalized["hours"] = normalized.delete("workhours")
|
|
98
|
+
changed = true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
return [data, false] unless changed
|
|
102
|
+
|
|
103
|
+
data = data.dup
|
|
104
|
+
data["slots"] = normalized
|
|
105
|
+
[data, true]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def config_file
|
|
109
|
+
CONFIG_FILE
|
|
110
|
+
end
|
|
111
|
+
|
|
67
112
|
def locale
|
|
68
113
|
load["locale"].to_s
|
|
69
114
|
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "time"
|
|
5
|
+
require "active_support/core_ext/time"
|
|
6
|
+
require "active_support/core_ext/date"
|
|
7
|
+
|
|
8
|
+
module FlycalCli
|
|
9
|
+
# Parses CLI date/time arguments in a locale-aware way.
|
|
10
|
+
#
|
|
11
|
+
# Always accepts ISO-like forms:
|
|
12
|
+
# YYYY-MM-DD, YYYY/MM/DD
|
|
13
|
+
# YYYY-MM-DDTHH:MM, YYYY-MM-DD HH:MM(:SS)
|
|
14
|
+
#
|
|
15
|
+
# With locale +it+: DD-MM-YYYY / DD/MM/YYYY (+ optional time)
|
|
16
|
+
# With locale +en+ (default): MM-DD-YYYY / MM/DD/YYYY (+ optional time)
|
|
17
|
+
class DateTimeParser
|
|
18
|
+
TIME_SUFFIX = '(?:\s+|T)(\d{1,2}):(\d{2})(?::(\d{2}))?'.freeze
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def parse(str, end_of_day: false, locale: nil)
|
|
22
|
+
value = str.to_s.strip
|
|
23
|
+
raise FlycalCli::Error, invalid_message(str) if value.empty?
|
|
24
|
+
|
|
25
|
+
loc = (locale || Locale.current_locale).to_s
|
|
26
|
+
formats = formats_for(loc)
|
|
27
|
+
|
|
28
|
+
formats.each do |pattern, order|
|
|
29
|
+
match = value.match(pattern)
|
|
30
|
+
next unless match
|
|
31
|
+
|
|
32
|
+
year, month, day, hour, min, sec = extract_parts(match, order)
|
|
33
|
+
return build_time(year, month, day, hour, min, sec, end_of_day: end_of_day)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Last resort: ISO8601 / Time.parse for full timestamps
|
|
37
|
+
begin
|
|
38
|
+
return Time.iso8601(value)
|
|
39
|
+
rescue ArgumentError
|
|
40
|
+
# fall through
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
parsed = Time.parse(value)
|
|
45
|
+
return end_of_day && !time_component?(value) ? end_of_day_for(parsed.to_date) : parsed
|
|
46
|
+
rescue ArgumentError
|
|
47
|
+
raise FlycalCli::Error, invalid_message(str)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_or_default(str, default: Time.now, end_of_day: false, locale: nil)
|
|
52
|
+
return default if str.nil? || str.to_s.strip.empty?
|
|
53
|
+
|
|
54
|
+
parse(str, end_of_day: end_of_day, locale: locale)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def formats_for(locale)
|
|
60
|
+
iso = [
|
|
61
|
+
[/\A(\d{4})[-\/](\d{1,2})[-\/](\d{1,2})#{TIME_SUFFIX}\z/i, %i[year month day hour min sec]],
|
|
62
|
+
[/\A(\d{4})[-\/](\d{1,2})[-\/](\d{1,2})\z/, %i[year month day]]
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
local =
|
|
66
|
+
if locale == "it"
|
|
67
|
+
[
|
|
68
|
+
[/\A(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})#{TIME_SUFFIX}\z/i, %i[day month year hour min sec]],
|
|
69
|
+
[/\A(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})\z/, %i[day month year]]
|
|
70
|
+
]
|
|
71
|
+
else
|
|
72
|
+
[
|
|
73
|
+
[/\A(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})#{TIME_SUFFIX}\z/i, %i[month day year hour min sec]],
|
|
74
|
+
[/\A(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})\z/, %i[month day year]]
|
|
75
|
+
]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
iso + local
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def extract_parts(match, order)
|
|
82
|
+
parts = {}
|
|
83
|
+
order.each_with_index do |key, idx|
|
|
84
|
+
parts[key] = match[idx + 1]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
[
|
|
88
|
+
parts[:year].to_i,
|
|
89
|
+
parts[:month].to_i,
|
|
90
|
+
parts[:day].to_i,
|
|
91
|
+
parts[:hour]&.to_i,
|
|
92
|
+
parts[:min]&.to_i,
|
|
93
|
+
parts[:sec]&.to_i
|
|
94
|
+
]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def build_time(year, month, day, hour, min, sec, end_of_day:)
|
|
98
|
+
date = Date.new(year, month, day)
|
|
99
|
+
|
|
100
|
+
if hour.nil?
|
|
101
|
+
return end_of_day_for(date) if end_of_day
|
|
102
|
+
return [date.to_time, Time.now].max if date == Date.today
|
|
103
|
+
|
|
104
|
+
return date.to_time
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
Time.local(year, month, day, hour, min || 0, sec || 0)
|
|
108
|
+
rescue ArgumentError
|
|
109
|
+
raise FlycalCli::Error, "Invalid date: #{year}-#{month}-#{day}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def end_of_day_for(date)
|
|
113
|
+
Time.local(date.year, date.month, date.day, 23, 59, 59)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def time_component?(value)
|
|
117
|
+
value.match?(/T|\d{1,2}:\d{2}/)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def invalid_message(str)
|
|
121
|
+
if Locale.current_locale.to_s == "it"
|
|
122
|
+
"Data non valida: #{str.inspect}. Usa YYYY-MM-DD, DD-MM-YYYY (anche con /) o con orario."
|
|
123
|
+
else
|
|
124
|
+
"Invalid date: #{str.inspect}. Use YYYY-MM-DD, MM-DD-YYYY (also with /), or with time."
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -10,7 +10,9 @@ module FlycalCli
|
|
|
10
10
|
"m" => :minutes, "min" => :minutes, "mins" => :minutes, "minute" => :minutes, "minutes" => :minutes,
|
|
11
11
|
"h" => :hours, "hr" => :hours, "hour" => :hours, "hours" => :hours,
|
|
12
12
|
"d" => :days, "day" => :days, "days" => :days,
|
|
13
|
-
"w" => :weeks, "week" => :weeks, "weeks" => :weeks
|
|
13
|
+
"w" => :weeks, "week" => :weeks, "weeks" => :weeks,
|
|
14
|
+
"month" => :months, "months" => :months,
|
|
15
|
+
"y" => :years, "year" => :years, "years" => :years
|
|
14
16
|
}.freeze
|
|
15
17
|
|
|
16
18
|
class << self
|
|
@@ -21,11 +23,18 @@ module FlycalCli
|
|
|
21
23
|
raise FlycalCli::Error, invalid_message(str) unless match
|
|
22
24
|
|
|
23
25
|
value = match[1].to_f
|
|
24
|
-
|
|
26
|
+
unit_key = match[2]
|
|
27
|
+
unit = UNITS[unit_key]
|
|
25
28
|
raise FlycalCli::Error, invalid_message(str) unless unit
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
# Ambiguous bare "m": treat as minutes (not months)
|
|
30
|
+
unit = :minutes if unit_key == "m"
|
|
31
|
+
|
|
32
|
+
# months/years are Integer-only ActiveSupport extensions
|
|
33
|
+
if %i[months years].include?(unit)
|
|
34
|
+
value.to_i.public_send(unit)
|
|
35
|
+
else
|
|
36
|
+
value.public_send(unit)
|
|
37
|
+
end
|
|
29
38
|
end
|
|
30
39
|
|
|
31
40
|
def to_seconds(str)
|
|
@@ -39,7 +48,7 @@ module FlycalCli
|
|
|
39
48
|
private
|
|
40
49
|
|
|
41
50
|
def invalid_message(str)
|
|
42
|
-
"Invalid duration: #{str.inspect}. Examples: 1h, 1
|
|
51
|
+
"Invalid duration: #{str.inspect}. Examples: 1h, 30 minutes, 3 days, 1 week, 2 months, 1 year"
|
|
43
52
|
end
|
|
44
53
|
end
|
|
45
54
|
end
|
data/lib/flycal_cli/locale.rb
CHANGED
|
@@ -31,6 +31,12 @@ module FlycalCli
|
|
|
31
31
|
months[idx] || date_or_time.strftime("%B")
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
def format_long_date(date_or_time)
|
|
35
|
+
t = date_or_time.respond_to?(:to_time) ? date_or_time.to_time : date_or_time
|
|
36
|
+
d = t.to_date
|
|
37
|
+
"#{day_abbr(d).downcase} #{d.day} #{month_name(d)} #{d.year}"
|
|
38
|
+
end
|
|
39
|
+
|
|
34
40
|
def current_locale
|
|
35
41
|
Thread.current[:flycal_locale_override] || Config.locale
|
|
36
42
|
rescue StandardError
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module FlycalCli
|
|
4
4
|
class SlotFinder
|
|
5
5
|
DEFAULT_WORKHOURS = [[9, 0, 18, 0]].freeze
|
|
6
|
+
STEP_SECONDS = 900
|
|
6
7
|
|
|
7
8
|
def initialize(
|
|
8
9
|
events:,
|
|
@@ -10,7 +11,9 @@ module FlycalCli
|
|
|
10
11
|
time_max:,
|
|
11
12
|
slot_duration_seconds:,
|
|
12
13
|
workhours: DEFAULT_WORKHOURS,
|
|
13
|
-
weekdays_only: true
|
|
14
|
+
weekdays_only: true,
|
|
15
|
+
free_before_seconds: 0,
|
|
16
|
+
free_after_seconds: 0
|
|
14
17
|
)
|
|
15
18
|
@events = events
|
|
16
19
|
@time_min = time_min
|
|
@@ -18,6 +21,8 @@ module FlycalCli
|
|
|
18
21
|
@slot_duration_seconds = slot_duration_seconds
|
|
19
22
|
@workhours = workhours
|
|
20
23
|
@weekdays_only = weekdays_only
|
|
24
|
+
@free_before_seconds = free_before_seconds
|
|
25
|
+
@free_after_seconds = free_after_seconds
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
def slots_by_day
|
|
@@ -30,7 +35,9 @@ module FlycalCli
|
|
|
30
35
|
|
|
31
36
|
busy = busy_intervals_for(date, day_start, day_end)
|
|
32
37
|
gaps = free_gaps(day_start, day_end, busy)
|
|
33
|
-
|
|
38
|
+
gaps.each do |gap_start, gap_end|
|
|
39
|
+
slots.concat(slots_in_gap(gap_start, gap_end))
|
|
40
|
+
end
|
|
34
41
|
end
|
|
35
42
|
result[date] = slots unless slots.empty?
|
|
36
43
|
end
|
|
@@ -74,7 +81,14 @@ module FlycalCli
|
|
|
74
81
|
intervals = @events.filter_map do |event|
|
|
75
82
|
interval_for_event(event, date, day_start, day_end)
|
|
76
83
|
end
|
|
77
|
-
merge_intervals(intervals)
|
|
84
|
+
merge_intervals(intervals.map { |start_at, end_at| pad_busy_interval(start_at, end_at) })
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def pad_busy_interval(start_at, end_at)
|
|
88
|
+
[
|
|
89
|
+
start_at - @free_before_seconds,
|
|
90
|
+
end_at + @free_after_seconds
|
|
91
|
+
]
|
|
78
92
|
end
|
|
79
93
|
|
|
80
94
|
def interval_for_event(event, date, day_start, day_end)
|
|
@@ -127,26 +141,30 @@ module FlycalCli
|
|
|
127
141
|
gaps
|
|
128
142
|
end
|
|
129
143
|
|
|
130
|
-
def
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
def slots_in_gap(start_at, end_at)
|
|
145
|
+
slots = []
|
|
146
|
+
cursor = round_up_15(start_at)
|
|
147
|
+
gap_limit = round_down_15(end_at)
|
|
148
|
+
|
|
149
|
+
while cursor + @slot_duration_seconds <= gap_limit
|
|
150
|
+
slots << [cursor, cursor + @slot_duration_seconds]
|
|
151
|
+
cursor += STEP_SECONDS
|
|
152
|
+
end
|
|
135
153
|
|
|
136
|
-
|
|
154
|
+
slots
|
|
137
155
|
end
|
|
138
156
|
|
|
139
157
|
def round_up_15(time)
|
|
140
158
|
sec = time.to_i
|
|
141
|
-
remainder = sec %
|
|
159
|
+
remainder = sec % STEP_SECONDS
|
|
142
160
|
return time if remainder.zero?
|
|
143
161
|
|
|
144
|
-
Time.at(sec + (
|
|
162
|
+
Time.at(sec + (STEP_SECONDS - remainder))
|
|
145
163
|
end
|
|
146
164
|
|
|
147
165
|
def round_down_15(time)
|
|
148
166
|
sec = time.to_i
|
|
149
|
-
Time.at(sec - (sec %
|
|
167
|
+
Time.at(sec - (sec % STEP_SECONDS))
|
|
150
168
|
end
|
|
151
169
|
|
|
152
170
|
def to_time(value)
|
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
module FlycalCli
|
|
4
4
|
class SlotFormatter
|
|
5
5
|
class << self
|
|
6
|
+
def format_header(from:, to:, duration:, calendars:, count:)
|
|
7
|
+
lines = []
|
|
8
|
+
lines << Locale.t(
|
|
9
|
+
"slots.header",
|
|
10
|
+
count: count,
|
|
11
|
+
from: underline(Locale.format_long_date(from)),
|
|
12
|
+
to: underline(Locale.format_long_date(to)),
|
|
13
|
+
duration: underline(duration)
|
|
14
|
+
)
|
|
15
|
+
calendars.each do |cal|
|
|
16
|
+
lines << cal[:name].to_s
|
|
17
|
+
end
|
|
18
|
+
lines << google_calendar_day_url(from)
|
|
19
|
+
lines.join("\n")
|
|
20
|
+
end
|
|
21
|
+
|
|
6
22
|
def format_output(slots_by_day)
|
|
7
23
|
lines = []
|
|
8
24
|
|
|
@@ -19,6 +35,15 @@ module FlycalCli
|
|
|
19
35
|
|
|
20
36
|
private
|
|
21
37
|
|
|
38
|
+
def underline(str)
|
|
39
|
+
"\e[4m#{str}\e[0m"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def google_calendar_day_url(from)
|
|
43
|
+
t = from.respond_to?(:to_time) ? from.to_time : from
|
|
44
|
+
"https://calendar.google.com/calendar/r/day/#{t.year}/#{t.month}/#{t.day}"
|
|
45
|
+
end
|
|
46
|
+
|
|
22
47
|
def day_header(date)
|
|
23
48
|
"#{Locale.day_name(date)} #{date.day}/#{date.month}"
|
|
24
49
|
end
|
data/lib/flycal_cli/version.rb
CHANGED
data/lib/flycal_cli.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "flycal_cli/config"
|
|
|
5
5
|
require "flycal_cli/locale"
|
|
6
6
|
require "flycal_cli/auth"
|
|
7
7
|
require "flycal_cli/duration_parser"
|
|
8
|
+
require "flycal_cli/date_time_parser"
|
|
8
9
|
require "flycal_cli/calendar_service"
|
|
9
10
|
require "flycal_cli/slot_finder"
|
|
10
11
|
require "flycal_cli/slot_formatter"
|
data/locales/en.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
|
+
"loading_calendars": "Loading calendars...",
|
|
3
4
|
"weekdays": {
|
|
4
5
|
"full": ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"],
|
|
5
6
|
"abbr": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
|
@@ -8,12 +9,35 @@
|
|
|
8
9
|
"full": ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
|
|
9
10
|
}
|
|
10
11
|
},
|
|
12
|
+
"config": {
|
|
13
|
+
"prompt": "Choose a configuration option:",
|
|
14
|
+
"options": {
|
|
15
|
+
"calendar_default": "calendar_default",
|
|
16
|
+
"exclude_calendars": "exclude_calendars",
|
|
17
|
+
"edit_config": "edit config"
|
|
18
|
+
},
|
|
19
|
+
"calendar_default": {
|
|
20
|
+
"prompt": "Choose the default calendar:",
|
|
21
|
+
"saved": "✓ Default calendar set to: %{name}"
|
|
22
|
+
},
|
|
23
|
+
"exclude_calendars": {
|
|
24
|
+
"prompt": "Choose calendars whose events block free slots:",
|
|
25
|
+
"saved": "✓ Exclude calendars set to: %{names}"
|
|
26
|
+
},
|
|
27
|
+
"edit": {
|
|
28
|
+
"failed": "Error: could not open config with %{editor}."
|
|
29
|
+
}
|
|
30
|
+
},
|
|
11
31
|
"slots": {
|
|
12
32
|
"no_available": "No available slots found.",
|
|
13
|
-
"no_calendar": "No calendar found. Run 'flycal
|
|
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}\nfor calendars",
|
|
35
|
+
"copied_to_clipboard": "✓ Slot list copied to clipboard (pbcopy)"
|
|
14
36
|
},
|
|
15
37
|
"errors": {
|
|
16
38
|
"not_connected": "You are not connected. Run 'flycal login' first.",
|
|
17
|
-
"duration_positive": "Error: --in must be a positive duration."
|
|
39
|
+
"duration_positive": "Error: --in must be a positive duration.",
|
|
40
|
+
"no_calendars": "No calendars found.",
|
|
41
|
+
"calendar_fetch": "Error in calendar %{calendar}: %{message}"
|
|
18
42
|
}
|
|
19
43
|
}
|
data/locales/it.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
|
+
"loading_calendars": "Caricamento calendari...",
|
|
3
4
|
"weekdays": {
|
|
4
5
|
"full": ["domenica", "lunedi", "martedi", "mercoledi", "giovedi", "venerdi", "sabato"],
|
|
5
6
|
"abbr": ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab"]
|
|
@@ -8,12 +9,35 @@
|
|
|
8
9
|
"full": ["gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre"]
|
|
9
10
|
}
|
|
10
11
|
},
|
|
12
|
+
"config": {
|
|
13
|
+
"prompt": "Scegli un'opzione di configurazione:",
|
|
14
|
+
"options": {
|
|
15
|
+
"calendar_default": "calendar_default",
|
|
16
|
+
"exclude_calendars": "exclude_calendars",
|
|
17
|
+
"edit_config": "edit config"
|
|
18
|
+
},
|
|
19
|
+
"calendar_default": {
|
|
20
|
+
"prompt": "Scegli il calendario predefinito:",
|
|
21
|
+
"saved": "✓ Calendario predefinito impostato su: %{name}"
|
|
22
|
+
},
|
|
23
|
+
"exclude_calendars": {
|
|
24
|
+
"prompt": "Scegli i calendari i cui eventi bloccano gli slot liberi:",
|
|
25
|
+
"saved": "✓ Calendari esclusi impostati su: %{names}"
|
|
26
|
+
},
|
|
27
|
+
"edit": {
|
|
28
|
+
"failed": "Errore: impossibile aprire il config con %{editor}."
|
|
29
|
+
}
|
|
30
|
+
},
|
|
11
31
|
"slots": {
|
|
12
32
|
"no_available": "Nessuno slot disponibile.",
|
|
13
|
-
"no_calendar": "Nessun calendario trovato. Esegui 'flycal
|
|
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}\nper i calendari",
|
|
35
|
+
"copied_to_clipboard": "✓ Lista slot copiata negli appunti (pbcopy)"
|
|
14
36
|
},
|
|
15
37
|
"errors": {
|
|
16
38
|
"not_connected": "Non sei connesso. Esegui prima 'flycal login'.",
|
|
17
|
-
"duration_positive": "Errore: --in deve essere una durata positiva."
|
|
39
|
+
"duration_positive": "Errore: --in deve essere una durata positiva.",
|
|
40
|
+
"no_calendars": "Nessun calendario trovato.",
|
|
41
|
+
"calendar_fetch": "Errore nel calendario %{calendar}: %{message}"
|
|
18
42
|
}
|
|
19
43
|
}
|
data/mcp/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# flycal MCP catalog
|
|
2
|
+
|
|
3
|
+
This directory contains a machine-readable description of **flycal-cli** commands for MCP (Model Context Protocol) servers.
|
|
4
|
+
|
|
5
|
+
## File
|
|
6
|
+
|
|
7
|
+
- [`tools.json`](./tools.json) — tools catalog with MCP-style `inputSchema`, CLI argv templates, auth/interactivity flags, and config keys.
|
|
8
|
+
|
|
9
|
+
## How an MCP server can use it
|
|
10
|
+
|
|
11
|
+
1. Load `tools.json` at startup.
|
|
12
|
+
2. Register each entry in `tools` with your MCP SDK (`server.tool(...)`), using `name`, `description`, and `inputSchema`.
|
|
13
|
+
3. On tool call, build the argv:
|
|
14
|
+
- static tools → `command.argv`
|
|
15
|
+
- parameterized tools → start from `command.argv_template`, then append pairs from `command.option_map` for each provided argument
|
|
16
|
+
- if `locale` is set → append `--locale <value>`
|
|
17
|
+
4. Run `flycal` as a subprocess and return stdout as text content.
|
|
18
|
+
5. Skip or gate tools with `"interactive": true` (`flycal_login`, `flycal_config`) unless a TTY is available.
|
|
19
|
+
|
|
20
|
+
## Suggested tools to expose
|
|
21
|
+
|
|
22
|
+
| MCP tool | CLI | Notes |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `flycal_search` | `flycal search ...` | Primary read tool |
|
|
25
|
+
| `flycal_slots` | `flycal slots --in ... --duration ...` | Free slots |
|
|
26
|
+
| `flycal_calendars` | `flycal calendars` | List name + id |
|
|
27
|
+
| `flycal_version` | `flycal version` | Health / version check |
|
|
28
|
+
|
|
29
|
+
Interactive / side-effect tools (`login`, `logout`, `config`, `update`) are documented in the catalog but are usually better left to the user terminal.
|