mailmate 1.6.0 → 1.8.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 +43 -16
- data/docs/Composing and threading.md +69 -0
- data/exe/mailmate-mcp +4 -0
- data/exe/mm-draft +4 -0
- data/exe/mm-mailboxes +4 -0
- data/exe/mm-modify +4 -0
- data/exe/mm-send +4 -0
- data/exe/mm-verify +4 -0
- data/exe/mmdiscover +4 -0
- data/exe/mmmessage +4 -0
- data/exe/mmopen +4 -0
- data/exe/mmsearch +4 -0
- data/exe/mmtags +4 -0
- data/lib/mailmate/cli/search.rb +361 -96
- data/lib/mailmate/cli/send.rb +156 -18
- data/lib/mailmate/cli/version_flag.rb +37 -0
- data/lib/mailmate/header_value.rb +42 -0
- data/lib/mailmate/mcp.rb +68 -29
- data/lib/mailmate/reply_prefill.rb +193 -0
- data/lib/mailmate/search_syntax.rb +316 -0
- data/lib/mailmate/version.rb +1 -1
- data/lib/mailmate.rb +5 -0
- metadata +11 -2
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
5
|
+
module Mailmate
|
|
6
|
+
# THE one description of quicksearch syntax. Both surfaces that teach the
|
|
7
|
+
# syntax — `mmsearch --help` and the MCP `search` tool description — render
|
|
8
|
+
# from the tables here, so the two can no longer drift apart (they already
|
|
9
|
+
# had: the CLI said "Nd|Nw|Nm|Ny (relative), or Y, Y-M, Y-M-D" while the MCP
|
|
10
|
+
# said "Y, Y-M, Y-M-D, or relative 1d/2w/3m/1y" — same rules, two wordings,
|
|
11
|
+
# two things to remember to update).
|
|
12
|
+
#
|
|
13
|
+
# Downstream consumers should POINT at these surfaces rather than restate
|
|
14
|
+
# them. A copy of the syntax in someone else's system prompt is a copy that
|
|
15
|
+
# goes stale the next time a modifier is added here.
|
|
16
|
+
module SearchSyntax
|
|
17
|
+
# [spec, meaning]. Order is the teaching order, not alphabetical.
|
|
18
|
+
MODIFIERS = [
|
|
19
|
+
["<term>", "common headers (from/to/cc/subject) OR body contains <term>"],
|
|
20
|
+
["f <term>", "from contains"],
|
|
21
|
+
["t <term>", "to/cc (recipients) contains"],
|
|
22
|
+
["c <term>", "cc contains"],
|
|
23
|
+
["s <term>", "subject contains"],
|
|
24
|
+
["a <term>", "any address header contains"],
|
|
25
|
+
["b <term>", "body contains"],
|
|
26
|
+
["m <term>", "common headers OR body (same as a bare term)"],
|
|
27
|
+
["d <date>", "received: Nh (rolling clock hours), Nd|Nw|Nm|Ny (N calendar units ending today; 1d = today), or Y, Y-M, Y-M-D"],
|
|
28
|
+
["T <tag>", "tag / IMAP keyword contains (K is a synonym)"],
|
|
29
|
+
["is:<state>", "message state: unread, read, flagged, replied, draft"],
|
|
30
|
+
["has:attachment", "root MIME is multipart/mixed (the standard attachment layout)"],
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
EXAMPLES = [
|
|
34
|
+
["f substack d 7d", "from Substack in the last 7 days"],
|
|
35
|
+
["s \"invoice due\" !draft", "subject has 'invoice due', not 'draft'"],
|
|
36
|
+
["d 2026-05", "received in May 2026"],
|
|
37
|
+
["d 2026-08-10", "received on one specific day"],
|
|
38
|
+
["d 1d", "received today (the default); d 2d = yesterday + today"],
|
|
39
|
+
["d 24h", "received in the last 24 hours (rolling, not calendar)"],
|
|
40
|
+
["d >=2026-05 d <2026-08", "received May through July 2026"],
|
|
41
|
+
["d 1h or 2026-08-09", "last hour, plus everything from Aug 9"],
|
|
42
|
+
["is:unread d 7d", "unread, received in the last 7 days"],
|
|
43
|
+
["T urgent", "tagged 'urgent'"],
|
|
44
|
+
].freeze
|
|
45
|
+
|
|
46
|
+
RULES = [
|
|
47
|
+
"Specs combine with AND; `or` separates alternatives, and AND binds tighter",
|
|
48
|
+
"(no parens): (f bob or f ann) s invoice = f bob s invoice or f ann s invoice.",
|
|
49
|
+
"After `or`, a bare first term inherits the modifier in force: d 2024 or 2025.",
|
|
50
|
+
"Wrap multi-word terms in \"double quotes\" (also how to search the word \"or\").",
|
|
51
|
+
"Prefix an operand with ! to negate: f !smith = from does NOT contain smith.",
|
|
52
|
+
"Negation works on dates too: d !3d = received MORE than 3 days ago.",
|
|
53
|
+
"Absolute dates compare: d >2026-08 (after Aug), d <2026-08 (before), also >= <=.",
|
|
54
|
+
"Slash dates are month-first American: d 8/9/2026 = Aug 9 (day-first: --european).",
|
|
55
|
+
"An impossible date combination (d >2026 d <2025) is an error, not 0 results.",
|
|
56
|
+
].freeze
|
|
57
|
+
|
|
58
|
+
# Search keys from OTHER mail systems (Gmail, Outlook, Apple Mail, IMAP
|
|
59
|
+
# dialects). Quicksearch has no `key:value` form at all, so a term like
|
|
60
|
+
# `date:today` is not a syntax error — it parses as a bare term and
|
|
61
|
+
# searches for the literal string "date:today" in headers and body, which
|
|
62
|
+
# matches nothing. That silence is the whole problem this list exists to
|
|
63
|
+
# break: an agent or a person gets an empty result set that is
|
|
64
|
+
# indistinguishable from "your mail really has nothing", and believes it.
|
|
65
|
+
# NOTE: is/has are absent — they are first-class quicksearch now
|
|
66
|
+
# (is:unread, has:attachment parse as native state specs).
|
|
67
|
+
FOREIGN_KEYS = %w[
|
|
68
|
+
after before older newer older_than newer_than on since until
|
|
69
|
+
date sent received time
|
|
70
|
+
from to cc bcc subject body
|
|
71
|
+
in label folder mailbox category filename
|
|
72
|
+
].freeze
|
|
73
|
+
|
|
74
|
+
# Spec placeholders for the zero-result hint, for foreign keys whose value
|
|
75
|
+
# translate() could NOT rewrite (e.g. `after:8am`). Keys absent here still
|
|
76
|
+
# get flagged, just without a suggested rewrite.
|
|
77
|
+
EQUIVALENTS = {
|
|
78
|
+
"from" => "f <term>", "to" => "t <term>", "cc" => "c <term>",
|
|
79
|
+
"subject" => "s <term>", "body" => "b <term>", "label" => "T <tag>",
|
|
80
|
+
"date" => "d <date>", "sent" => "d <date>", "received" => "d <date>",
|
|
81
|
+
"on" => "d <date>", "after" => "d >=YYYY-MM-DD", "since" => "d >=YYYY-MM-DD",
|
|
82
|
+
"before" => "d <YYYY-MM-DD", "until" => "d <=YYYY-MM-DD",
|
|
83
|
+
"newer_than" => "d Nd", "older_than" => "d !Nd",
|
|
84
|
+
"newer" => "d Nd", "older" => "d !Nd",
|
|
85
|
+
}.freeze
|
|
86
|
+
|
|
87
|
+
# Foreign header-ish keys with a direct quicksearch spec. The value
|
|
88
|
+
# carries over unchanged, so these translate regardless of what it is.
|
|
89
|
+
HEADER_EQUIV = {
|
|
90
|
+
"from" => "f", "to" => "t", "cc" => "c",
|
|
91
|
+
"subject" => "s", "body" => "b", "label" => "T",
|
|
92
|
+
}.freeze
|
|
93
|
+
|
|
94
|
+
# The --help table for the translator. Symbolic, not computed — <N> is
|
|
95
|
+
# resolved against the current date at translation time.
|
|
96
|
+
TRANSLATIONS_HELP = [
|
|
97
|
+
["from:bob (to: cc: subject: body: label:)", "f bob (t c s b T)"],
|
|
98
|
+
["-from:bob or !from:bob", "f !bob"],
|
|
99
|
+
["date:today / date:yesterday", "d 1d / d <that day>"],
|
|
100
|
+
["date:2026-03-05 or date:3/5/2026 (M/D/Y)", "d 2026-03-05"],
|
|
101
|
+
["newer_than:2d / older_than:2w", "d 2d / d !2w"],
|
|
102
|
+
["after:2026-05 or since:2026-05", "d >=2026-05"],
|
|
103
|
+
["before:2026-08 / until:2026-08", "d <2026-08 / d <=2026-08"],
|
|
104
|
+
].freeze
|
|
105
|
+
|
|
106
|
+
module_function
|
|
107
|
+
|
|
108
|
+
# The shared syntax reference, indented for embedding. Used verbatim by
|
|
109
|
+
# `mmsearch --help` and by the MCP tool description.
|
|
110
|
+
def reference(indent: " ")
|
|
111
|
+
width = MODIFIERS.map { |spec, _| spec.length }.max
|
|
112
|
+
lines = []
|
|
113
|
+
RULES.each { |r| lines << "#{indent}#{r}" }
|
|
114
|
+
lines << ""
|
|
115
|
+
MODIFIERS.each { |spec, meaning| lines << "#{indent} #{spec.ljust(width)} #{meaning}" }
|
|
116
|
+
lines << ""
|
|
117
|
+
lines << "#{indent}Examples:"
|
|
118
|
+
ex_width = EXAMPLES.map { |q, _| q.length }.max
|
|
119
|
+
EXAMPLES.each { |q, meaning| lines << "#{indent} #{q.ljust(ex_width)} #{meaning}" }
|
|
120
|
+
lines.join("\n")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# One token: an (optionally key:-prefixed) quoted string, or a bare run
|
|
124
|
+
# of non-space. Quoted regions survive as single tokens so translate()
|
|
125
|
+
# can leave a deliberate literal search (`s "date:today"`) alone.
|
|
126
|
+
TOKEN_RX = /(?:[-!]?[A-Za-z_]+:)?"[^"]*"|(?:[-!]?[A-Za-z_]+:)?'[^']*'|\S+/
|
|
127
|
+
|
|
128
|
+
# Rewrite foreign `key:value` tokens to their exact quicksearch
|
|
129
|
+
# equivalent, leaving everything else byte-for-byte intact. Returns
|
|
130
|
+
# [query, notes] where notes is [[original_token, replacement], ...] —
|
|
131
|
+
# callers MUST surface the notes (stderr, tool result); a silent rewrite
|
|
132
|
+
# would show the reader a query that never ran.
|
|
133
|
+
#
|
|
134
|
+
# Only rewrites where the equivalence is exact. A foreign key whose value
|
|
135
|
+
# can't be translated faithfully (`after:8am`, `date:next week`) stays in
|
|
136
|
+
# the query as literal text, and zero_result_hint still flags it there.
|
|
137
|
+
def translate(query, today: Date.today, european: false)
|
|
138
|
+
notes = []
|
|
139
|
+
translated = query.to_s.gsub(TOKEN_RX) do |token|
|
|
140
|
+
replacement = translate_token(token, today, european)
|
|
141
|
+
notes << [token, replacement] if replacement
|
|
142
|
+
replacement || token
|
|
143
|
+
end
|
|
144
|
+
[translated, notes]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# The stderr/tool-result announcement for a rewritten query. nil when
|
|
148
|
+
# nothing was rewritten.
|
|
149
|
+
def translation_notice(notes)
|
|
150
|
+
return nil if notes.empty?
|
|
151
|
+
|
|
152
|
+
width = notes.map { |from, _| from.length }.max
|
|
153
|
+
lines = ["translated foreign search syntax to MailMate quicksearch (`mmsearch --help`):"]
|
|
154
|
+
notes.each { |from, to| lines << " #{from.ljust(width)} -> #{to}" }
|
|
155
|
+
lines.join("\n")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# The --help table, indented for embedding.
|
|
159
|
+
def translation_reference(indent: " ")
|
|
160
|
+
width = TRANSLATIONS_HELP.map { |from, _| from.length }.max
|
|
161
|
+
lines = TRANSLATIONS_HELP.map { |from, to| "#{indent} #{from.ljust(width)} -> #{to}" }
|
|
162
|
+
lines << ""
|
|
163
|
+
lines << "#{indent}Keys with no equivalent (is: has: in: filename: ...) are searched as"
|
|
164
|
+
lines << "#{indent}literal text; an empty result will call them out."
|
|
165
|
+
lines.join("\n")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Foreign `key:value` tokens in a query, lowercased keys, in order of
|
|
169
|
+
# appearance and de-duplicated. Quoted regions are skipped: a deliberate
|
|
170
|
+
# search for the literal text `s "date:today"` is not a mistake.
|
|
171
|
+
def foreign_tokens(query)
|
|
172
|
+
unquoted = query.to_s.gsub(/"[^"]*"|'[^']*'/, " ")
|
|
173
|
+
unquoted.scan(/(?<![\w-])!?([A-Za-z_]+):(\S*)/).filter_map do |key, value|
|
|
174
|
+
k = key.downcase
|
|
175
|
+
next unless FOREIGN_KEYS.include?(k)
|
|
176
|
+
["#{key}:#{value}", k]
|
|
177
|
+
end.uniq { |_token, k| k }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# The advisory a caller should print when a search matched NOTHING and the
|
|
181
|
+
# query carries foreign syntax. nil when there is nothing to say — an
|
|
182
|
+
# ordinary empty result stays silent, because polling for mail that has
|
|
183
|
+
# not arrived yet is a normal, correct thing to do.
|
|
184
|
+
def zero_result_hint(query)
|
|
185
|
+
tokens = foreign_tokens(query)
|
|
186
|
+
return nil if tokens.empty?
|
|
187
|
+
|
|
188
|
+
quoted = tokens.map { |token, _| "`#{token}`" }.join(", ")
|
|
189
|
+
lines = ["0 results, and #{quoted} #{tokens.size == 1 ? "is not" : "are not"} " \
|
|
190
|
+
"MailMate quicksearch syntax — it was searched for as literal text."]
|
|
191
|
+
tokens.each do |token, key|
|
|
192
|
+
eq = EQUIVALENTS[key] or next
|
|
193
|
+
lines << " #{token} -> #{eq}"
|
|
194
|
+
end
|
|
195
|
+
lines << "Run `mmsearch --help` for the full syntax."
|
|
196
|
+
lines.join("\n")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# ---- translation internals -------------------------------------------
|
|
200
|
+
|
|
201
|
+
# nil = not a rewritable token (not key:value, unknown key, or a value
|
|
202
|
+
# with no faithful equivalent).
|
|
203
|
+
def translate_token(token, today, european = false)
|
|
204
|
+
m = token.match(/\A(?<neg>[-!])?(?<key>[A-Za-z_]+):(?<value>.+)\z/m)
|
|
205
|
+
return nil unless m
|
|
206
|
+
|
|
207
|
+
key = m[:key].downcase
|
|
208
|
+
value = unquote(m[:value])
|
|
209
|
+
return nil if value.empty?
|
|
210
|
+
|
|
211
|
+
if (spec = HEADER_EQUIV[key])
|
|
212
|
+
negated = !m[:neg].nil?
|
|
213
|
+
# `f !"a b"` won't tokenize (the ! detaches the quotes) — leave a
|
|
214
|
+
# negated multi-word value alone rather than emit a broken spec.
|
|
215
|
+
return nil if negated && value =~ /\s/
|
|
216
|
+
operand = value =~ /\s/ ? "\"#{value}\"" : value
|
|
217
|
+
return "#{spec} #{negated ? "!" : ""}#{operand}"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Date keys: Gmail has no negated date form, so a -/! prefix here means
|
|
221
|
+
# the caller is inventing syntax — don't guess at intent.
|
|
222
|
+
return nil if m[:neg]
|
|
223
|
+
|
|
224
|
+
case key
|
|
225
|
+
when "date", "on", "sent", "received", "time"
|
|
226
|
+
translate_point_date(value, today, european)
|
|
227
|
+
when "after", "since"
|
|
228
|
+
translate_after(value, today, european)
|
|
229
|
+
when "before"
|
|
230
|
+
translate_before(value, today, "<", european)
|
|
231
|
+
when "until"
|
|
232
|
+
translate_before(value, today, "<=", european)
|
|
233
|
+
when "newer_than", "newer"
|
|
234
|
+
(rel = parse_relative(value)) && "d #{rel}"
|
|
235
|
+
when "older_than", "older"
|
|
236
|
+
(rel = parse_relative(value)) && "d !#{rel}"
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def unquote(value)
|
|
241
|
+
case value
|
|
242
|
+
when /\A"(.*)"\z/m, /\A'(.*)'\z/m then Regexp.last_match(1)
|
|
243
|
+
else value
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# A day-, month-, or year-precision point in time. `d <period>` matches
|
|
248
|
+
# exactly that period in the engine, so these are exact.
|
|
249
|
+
def translate_point_date(value, today, european = false)
|
|
250
|
+
v = value.downcase
|
|
251
|
+
return "d 1d" if v == "today"
|
|
252
|
+
return "d #{(today - 1).strftime("%Y-%m-%d")}" if v == "yesterday"
|
|
253
|
+
# `date:8/10/2026-today` (seen in real transcripts): a range whose end
|
|
254
|
+
# is now IS an after-window.
|
|
255
|
+
return translate_after(v.delete_suffix("-today"), today, european) if v.end_with?("-today")
|
|
256
|
+
return "d #{v}" if v =~ /\A\d+[dwmy]\z/
|
|
257
|
+
|
|
258
|
+
(period = normalize_period(v, european)) && "d #{period}"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Gmail's after: includes the named day; since: likewise → >=.
|
|
262
|
+
def translate_after(value, today, european = false)
|
|
263
|
+
v = value.downcase
|
|
264
|
+
return "d 1d" if v == "today"
|
|
265
|
+
return "d 2d" if v == "yesterday"
|
|
266
|
+
|
|
267
|
+
(period = normalize_period(v, european)) && "d >=#{period}"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Gmail's before: excludes the named day → <. until: includes it → <=.
|
|
271
|
+
def translate_before(value, today, op, european = false)
|
|
272
|
+
v = value.downcase
|
|
273
|
+
v = today.strftime("%Y-%m-%d") if v == "today"
|
|
274
|
+
v = (today - 1).strftime("%Y-%m-%d") if v == "yesterday"
|
|
275
|
+
|
|
276
|
+
(period = normalize_period(v, european)) && "d #{op}#{period}"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# "2026", "2026-05", "2026-03-05", "3/5/2026", "2026/3/5" → the
|
|
280
|
+
# normalized absolute period string quicksearch expects, or nil.
|
|
281
|
+
def normalize_period(value, european = false)
|
|
282
|
+
if (day = parse_day(value, european))
|
|
283
|
+
day.strftime("%Y-%m-%d")
|
|
284
|
+
elsif value =~ %r{\A(\d{4})[-/.](\d{1,2})\z}
|
|
285
|
+
format("%04d-%02d", Regexp.last_match(1).to_i, Regexp.last_match(2).to_i)
|
|
286
|
+
elsif value =~ /\A\d{4}\z/
|
|
287
|
+
value
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Gmail relative units (d/m/y, plus w) carry over as-is: `d N<u>` uses
|
|
292
|
+
# the same calendar arithmetic.
|
|
293
|
+
def parse_relative(value)
|
|
294
|
+
value =~ /\A(\d+)\s*([dwmy])\z/ ? "#{Regexp.last_match(1)}#{Regexp.last_match(2)}" : nil
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Y-M-D (any of - / . separators), or slash-dates with a trailing
|
|
298
|
+
# 4-digit year — US M/D/Y by default, D/M/Y when european. Two-digit
|
|
299
|
+
# years are ambiguous across dialects — refused rather than guessed.
|
|
300
|
+
def parse_day(value, european = false)
|
|
301
|
+
parts = value.split(%r{[-/.]})
|
|
302
|
+
return nil unless parts.size == 3 && parts.all? { |p| p =~ /\A\d+\z/ }
|
|
303
|
+
|
|
304
|
+
y, m, d =
|
|
305
|
+
if parts[0].length == 4
|
|
306
|
+
[parts[0], parts[1], parts[2]]
|
|
307
|
+
elsif parts[2].length == 4
|
|
308
|
+
european ? [parts[2], parts[1], parts[0]] : [parts[2], parts[0], parts[1]]
|
|
309
|
+
end
|
|
310
|
+
return nil unless y
|
|
311
|
+
|
|
312
|
+
y, m, d = y.to_i, m.to_i, d.to_i
|
|
313
|
+
Date.valid_date?(y, m, d) ? Date.new(y, m, d) : nil
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
data/lib/mailmate/version.rb
CHANGED
data/lib/mailmate.rb
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
# Mailmate::PartLookup.body_parts_of(envelope_id) → child body-part-ids of an envelope
|
|
13
13
|
# Mailmate::EmlLookup.path_for(eml_id) → eml-id → absolute path
|
|
14
14
|
# Mailmate::HeaderReader.header(path, name) → read one header from an .eml
|
|
15
|
+
# Mailmate::HeaderValue.sanitize(v) → CR/LF-safe --header value (ALL header paths use this)
|
|
16
|
+
# Mailmate::ReplyPrefill.build(id, mode:) → reply/reply-all/forward fields from a parent
|
|
15
17
|
# Mailmate::MidUrl.for(message_id) → build a mid:%3C...%3E URL
|
|
16
18
|
# Mailmate::DuplicateScanner.duplicates → Hash{Message-ID => Array<eml_id>}
|
|
17
19
|
# Mailmate::AppleScriptDriver.new(...) → drive MailMate via AppleScript
|
|
@@ -35,6 +37,8 @@ require_relative "mailmate/identity"
|
|
|
35
37
|
require_relative "mailmate/header_reader"
|
|
36
38
|
require_relative "mailmate/mid_url"
|
|
37
39
|
require_relative "mailmate/eml_lookup"
|
|
40
|
+
require_relative "mailmate/header_value"
|
|
41
|
+
require_relative "mailmate/reply_prefill"
|
|
38
42
|
require_relative "mailmate/duplicate_scanner"
|
|
39
43
|
require_relative "mailmate/applescript_driver"
|
|
40
44
|
require_relative "mailmate/ast"
|
|
@@ -50,6 +54,7 @@ require_relative "mailmate/mailbox_graph"
|
|
|
50
54
|
require_relative "mailmate/source_resolver"
|
|
51
55
|
require_relative "mailmate/var_resolver"
|
|
52
56
|
require_relative "mailmate/filter_classifier"
|
|
57
|
+
require_relative "mailmate/search_syntax"
|
|
53
58
|
|
|
54
59
|
module Mailmate
|
|
55
60
|
# First-run bootstrap. If ~/.config/mailmate/config.yml is missing,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mailmate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Murphy-Dye
|
|
@@ -110,6 +110,7 @@ files:
|
|
|
110
110
|
- LICENSE.txt
|
|
111
111
|
- README.md
|
|
112
112
|
- config.yml.example
|
|
113
|
+
- docs/Composing and threading.md
|
|
113
114
|
- exe/mailmate-mcp
|
|
114
115
|
- exe/mm-draft
|
|
115
116
|
- exe/mm-mailboxes
|
|
@@ -135,6 +136,7 @@ files:
|
|
|
135
136
|
- lib/mailmate/cli/send.rb
|
|
136
137
|
- lib/mailmate/cli/tags.rb
|
|
137
138
|
- lib/mailmate/cli/verify.rb
|
|
139
|
+
- lib/mailmate/cli/version_flag.rb
|
|
138
140
|
- lib/mailmate/config.rb
|
|
139
141
|
- lib/mailmate/duplicate_scanner.rb
|
|
140
142
|
- lib/mailmate/eml_lookup.rb
|
|
@@ -142,6 +144,7 @@ files:
|
|
|
142
144
|
- lib/mailmate/filter_classifier.rb
|
|
143
145
|
- lib/mailmate/flag_check.rb
|
|
144
146
|
- lib/mailmate/header_reader.rb
|
|
147
|
+
- lib/mailmate/header_value.rb
|
|
145
148
|
- lib/mailmate/identity.rb
|
|
146
149
|
- lib/mailmate/index_reader.rb
|
|
147
150
|
- lib/mailmate/lexer.rb
|
|
@@ -153,12 +156,18 @@ files:
|
|
|
153
156
|
- lib/mailmate/parser.rb
|
|
154
157
|
- lib/mailmate/part_lookup.rb
|
|
155
158
|
- lib/mailmate/platform_error.rb
|
|
159
|
+
- lib/mailmate/reply_prefill.rb
|
|
160
|
+
- lib/mailmate/search_syntax.rb
|
|
156
161
|
- lib/mailmate/source_resolver.rb
|
|
157
162
|
- lib/mailmate/var_resolver.rb
|
|
158
163
|
- lib/mailmate/version.rb
|
|
164
|
+
homepage: https://github.com/brianmd/mailmate
|
|
159
165
|
licenses:
|
|
160
166
|
- MIT
|
|
161
|
-
metadata:
|
|
167
|
+
metadata:
|
|
168
|
+
source_code_uri: https://github.com/brianmd/mailmate
|
|
169
|
+
bug_tracker_uri: https://github.com/brianmd/mailmate/issues
|
|
170
|
+
documentation_uri: https://github.com/brianmd/mailmate#readme
|
|
162
171
|
rdoc_options: []
|
|
163
172
|
require_paths:
|
|
164
173
|
- lib
|