rscontacts 0.2.0

Managed your google contacts
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
pub mod commands;
pub mod helpers;

use clap::{CommandFactory, Parser, Subcommand};

#[derive(Parser)]
#[command(name = "rscontacts")]
#[command(about = "Google Contacts CLI tool")]
pub struct Cli {
    /// Print a message when a transient API error triggers a retry
    #[arg(long, global = true)]
    pub transport_errors: bool,

    #[command(subcommand)]
    pub command: Commands,
}

#[derive(Subcommand)]
pub enum Commands {
    /// Run all checks
    #[command(name = "all-checks")]
    AllChecks {
        /// Fix all issues found
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
        /// Only show error counts per check, no details
        #[arg(long)]
        stats: bool,
        /// Show each check name as it runs
        #[arg(short, long)]
        verbose: bool,
        /// Country code to prepend for phone country code check (without +)
        #[arg(long, default_value = "972")]
        country: String,
    },
    /// Authenticate with Google (opens browser for OAuth2 consent)
    Auth {
        /// Don't open browser automatically; print URL instead
        #[arg(long)]
        no_browser: bool,
        /// Force re-authentication even if a token is already cached
        #[arg(long)]
        force: bool,
    },
    /// Check that all company fields are in the known companies list from config
    CheckContactCompanyExists {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check that every company in the config has at least one contact
    CheckContactCompanyKnown {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Print contacts that share the same display name
    CheckContactDisplaynameDuplicate {
        /// Interactively fix each duplicate (rename/delete/skip)
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Print contacts with an empty display name
    CheckContactNoDisplayname {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Print contacts with invalid or uppercase email addresses
    CheckContactEmail {
        /// Automatically lowercase emails with uppercase letters
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Print contacts that have the same email address attached twice
    CheckContactEmailDuplicate {
        /// Interactively remove duplicate email addresses
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check family names against allow regex defined in config.toml
    CheckContactFamilyNameRegexp {
        /// Interactively fix each flagged contact (rename/delete/skip)
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check that every given name in the config has at least one contact
    CheckContactGivenNameExists {
        /// Interactively fix each flagged entry
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check that all given names are in the allowed list from config
    CheckContactGivenNameKnown {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check given names against allow regex defined in config.toml
    CheckContactGivenNameRegexp {
        /// Interactively fix each flagged contact (swap/rename/delete/skip)
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Print labels (contact groups) that have no contacts
    CheckContactLabelNophone {
        /// Delete empty labels
        #[arg(long)]
        fix: bool,
        /// Show what would be deleted without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check contact labels (groups) against allow regex defined in config.toml
    CheckContactLabelRegexp {
        /// Interactively rename labels that don't match
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check contacts that have no given name but have a family name
    CheckContactNoGivenName {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check contacts that have no type tag (type:Person or type:Company)
    CheckContactNoIdentity {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Print contacts not assigned to any label (contact group)
    CheckContactNoLabel {
        /// Interactively fix: delete contact or assign a label
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check that no contact has a middle name set
    CheckContactNoMiddleName {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check that no contact has a nickname set
    CheckContactNoNickname {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check suffixes against allow regex (default: numeric)
    CheckContactSuffixRegexp {
        /// Interactively fix each flagged contact (rename/delete/skip)
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check that contacts tagged type:Company have given name equal to company field
    CheckContactTypeCompanyGivenName {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Automatically set given name to company field value
        #[arg(long)]
        auto_fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check that contacts tagged type:Company have a matching company:<name> label
    CheckContactTypeCompanyNoLabel {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Automatically create and assign the company:<name> label
        #[arg(long)]
        auto_fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check that contacts tagged type:Company have their company field set
    CheckContactTypeCompanyNoCompany {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check that every contact has exactly one of type:Person or type:Company labels
    CheckContactType {
        /// Interactively fix each flagged contact
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Check that contacts have correct country:<Name> labels matching their phone country codes
    CheckPhoneCountryLabel {
        /// Automatically add/remove country labels
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Print contacts with phone numbers missing a country code
    CheckPhoneCountrycode {
        /// Fix by prepending country code
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
        /// Country code to prepend (without +)
        #[arg(long, default_value = "972")]
        country: String,
    },
    /// Print contacts that have the same phone number attached twice
    CheckPhoneDuplicate {
        /// Interactively remove duplicate phone numbers
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Print phone numbers not in +CC-NUMBER format
    CheckPhoneFormat {
        /// Fix phone numbers to +CC-NUMBER format
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
        /// Country code to use when formatting
        #[arg(long, default_value = "972")]
        country: String,
    },
    /// Print contacts with non-English phone labels
    CheckPhoneLabelEnglish {
        /// Interactively fix non-English phone labels
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Print contacts with phone numbers missing a label (mobile/home/work/etc)
    CheckPhoneLabelMissing {
        /// Interactively fix phones without labels
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Find and merge contacts that share the same email address
    MergeByEmail {
        /// Interactively merge each duplicate group
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Find and merge contacts that share the same phone number
    MergeByPhone {
        /// Interactively merge each duplicate group
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Move numeric family names to suffix for contacts that have no suffix
    MoveFamilyToSuffix {
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Move numeric suffixes to family name for contacts that have no family name
    MoveSuffixToFamily {
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Compact suffixes for contacts sharing the same base name (given + family)
    CompactSuffixesForContacts {
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Generate shell completions
    Complete {
        /// Shell to generate completions for
        #[arg(value_enum)]
        shell: clap_complete::Shell,
    },
    /// Interactively edit a contact
    EditContact {
        /// Name (or part of name) to search for
        name: String,
    },
    /// Export all contacts as JSON
    ExportJson {
        /// Only output displayName and resourceName
        #[arg(long)]
        short: bool,
    },
    /// Generate a default config file at ~/.config/rscontacts/config.toml
    InitConfig {
        /// Overwrite existing config file
        #[arg(long)]
        force: bool,
    },
    /// List all contacts
    List {
        /// Also show email addresses
        #[arg(long)]
        emails: bool,
        /// Also show contact labels (contact group memberships)
        #[arg(long)]
        labels: bool,
        /// Only show starred contacts
        #[arg(long)]
        starred: bool,
    },
    /// Sync Google Contacts to GNOME Contacts (Evolution Data Server)
    SyncGnomeContacts {
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Remove a contact label (group) from all contacts that have it
    RemoveLabelFromAllContacts {
        /// The label name to remove (case-insensitive)
        label: String,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Review all emails with a specific label (e.g. "Work")
    ReviewEmailLabel {
        /// The email label to review (case-insensitive)
        label: String,
        /// Interactively fix each email (delete/relabel/skip)
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Review all phones with a specific label (e.g. "Work Fax")
    ReviewPhoneLabel {
        /// The phone label to review (case-insensitive)
        label: String,
        /// Interactively fix each phone (delete/relabel/skip)
        #[arg(long)]
        fix: bool,
        /// Show what would be changed without modifying anything
        #[arg(long)]
        dry_run: bool,
    },
    /// Show all details about a specific contact
    ShowContact {
        /// Name (or part of name) to search for
        name: String,
    },
    /// Show all contact labels (contact groups) in use
    ShowContactLabels,
    /// Show all distinct email labels in use
    ShowEmailLabels,
    /// Show all distinct phone labels in use
    ShowPhoneLabels,
    /// Test connectivity to the Google People API
    TestConnect,
    /// Print version information
    Version,
}

pub fn generate_completions(shell: clap_complete::Shell) {
    clap_complete::generate(shell, &mut Cli::command(), "rscontacts", &mut std::io::stdout());
}