@bobfrankston/gcards 0.1.2 → 0.1.5

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.
@@ -13,7 +13,15 @@
13
13
  "Bash(gh repo create:*)",
14
14
  "Bash(gh repo edit:*)",
15
15
  "Bash(git push:*)",
16
- "Bash(npm view:*)"
16
+ "Bash(npm view:*)",
17
+ "Bash(npx tsc:*)",
18
+ "Bash(grep:*)",
19
+ "Bash(node gfix.ts:*)",
20
+ "Bash(node --import tsx gcards.ts:*)",
21
+ "Bash(npm install:*)",
22
+ "Bash(npx tsx:*)",
23
+ "WebFetch(domain:lh3.googleusercontent.com)",
24
+ "Bash(curl:*)"
17
25
  ],
18
26
  "deny": [],
19
27
  "ask": []
package/.gitattributes ADDED
@@ -0,0 +1 @@
1
+ * text=auto eol=lf
@@ -1,3 +1,23 @@
1
- {
2
- "workbench.colorTheme": "Visual Studio 2019 Dark"
1
+ {
2
+ "workbench.colorTheme": "Visual Studio 2019 Dark",
3
+ "workbench.colorCustomizations": {
4
+ "activityBar.activeBackground": "#0ae04a",
5
+ "activityBar.background": "#0ae04a",
6
+ "activityBar.foreground": "#15202b",
7
+ "activityBar.inactiveForeground": "#15202b99",
8
+ "activityBarBadge.background": "#9266f8",
9
+ "activityBarBadge.foreground": "#15202b",
10
+ "commandCenter.border": "#e7e7e799",
11
+ "sash.hoverBorder": "#0ae04a",
12
+ "statusBar.background": "#08af3a",
13
+ "statusBar.foreground": "#e7e7e7",
14
+ "statusBarItem.hoverBackground": "#0ae04a",
15
+ "statusBarItem.remoteBackground": "#08af3a",
16
+ "statusBarItem.remoteForeground": "#e7e7e7",
17
+ "titleBar.activeBackground": "#08af3a",
18
+ "titleBar.activeForeground": "#e7e7e7",
19
+ "titleBar.inactiveBackground": "#08af3a99",
20
+ "titleBar.inactiveForeground": "#e7e7e799"
21
+ },
22
+ "peacock.color": "#08af3a"
3
23
  }
package/README.md CHANGED
@@ -1,61 +1,229 @@
1
- # Google Contacts Cleanup Tool (gcards)
2
-
3
- ## Goal
4
- Intelligent cleanup and merging of Google Contacts using the People API.
5
-
6
- ## Roadmap
7
- - **Current**: CLI tool
8
- - **Future**: GUI interface
9
- - **Eventually**: Own contacts database (vcf as intermediate format)
10
-
11
- ## Features
12
- - Fetch contacts via Google People API (preserves `resourceName` for reconciliation)
13
- - Convert to simplified JSON structure for analysis
14
- - Identify duplicates: **exact match first** (many blatant dupes), fuzzy matching optional/later
15
- - Flag test entries (pattern: `m-\d+@bob\.ma` for "Bob Frankston")
16
- - Generate merge candidates with confidence scores
17
- - Apply changes back via API using `resourceName`
18
- - Export to vCard format (`vcf/` directory)
19
-
20
- ## Approach
21
- 1. **Auth**: OAuth2 with Google People API scope
22
- 2. **Fetch**: Full fetch first, save `syncToken` for incremental updates
23
- 3. **Transform**: Convert `GooglePerson[]` → `LocalContact[]` (see `types.ts`)
24
- 4. **Analyze**: Exact duplicate detection first, test entry flagging
25
- 5. **Review**: Output candidates for user review
26
- 6. **Apply**: Update/delete via API using `resourceName`
27
- 7. **Export**: Save to `vcf/` as vCards for local backup/future DB
28
- 8. **Log**: Track deletions in `deletion-log.json` to detect re-additions
29
-
30
- ## Sync Token Usage
31
- - After full fetch, save `nextSyncToken`
32
- - Subsequent fetches: pass `syncToken` → get only new/changed/deleted
33
- - Deleted entries appear with `metadata.deleted: true`
34
- - Token expires ~30 days → fall back to full fetch (410 GONE error)
35
-
36
- ## Deletion Logging
37
- Maintain `deletion-log.json` with:
38
- - `resourceName`, `displayName`, `reason`, `deletedAt`, `originalData`
39
- - Allows detecting if cleaned-up contact reappears (re-synced, re-added)
40
-
41
- ## Setup
42
- 1. Create Google Cloud project
43
- 2. Enable People API
44
- 3. Create OAuth2 credentials (Desktop app)
45
- 4. Download `credentials.json` to this directory
46
-
47
- ## Directory Structure
48
- ```
49
- gcards/
50
- ├── README.md
51
- ├── types.ts # TypeScript definitions
52
- ├── credentials.json # OAuth2 credentials (gitignored)
53
- ├── token.json # OAuth tokens (gitignored)
54
- ├── sync-token.json # Sync token for incremental fetch
55
- ├── deletion-log.json # Track deleted contacts
56
- └── vcf/ # vCard exports for future DB
57
- ```
58
-
59
- ## Test Entry Pattern
60
-
61
- Emails matching `m-[2506|2512]+@bob\.ma` with name "Bob Frankston" are test business cards to be flagged/removed.
1
+ # gcards - Google Contacts Management Tool
2
+
3
+ A CLI tool for syncing, managing, and cleaning up Google Contacts.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @bobfrankston/gcards
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # First time - specify your Gmail username
15
+ gcards sync --user yourname
16
+
17
+ # Subsequent runs remember the user
18
+ gcards sync
19
+ gcards push
20
+ ```
21
+
22
+ ## Commands
23
+
24
+ ### gcards
25
+
26
+ Main sync and push operations (Google API):
27
+
28
+ ```bash
29
+ gcards sync [-u user] [-a] # Download contacts from Google
30
+ gcards push [-u user] [-a] # Push local changes to Google
31
+ gcards -a # Process all users
32
+ ```
33
+
34
+ ### gfix
35
+
36
+ One-time cleanup operations (local files only):
37
+
38
+ ```bash
39
+ gfix inspect -u user # Preview all standard fixes (no changes)
40
+ gfix apply -u user # Apply all standard fixes
41
+
42
+ gfix names -u user # Preview name parsing + dup phone/email removal
43
+ gfix names -u user --apply # Parse names, remove dup phones/emails
44
+
45
+ gfix birthday -u user # Preview birthday extraction
46
+ gfix birthday -u user --apply # Extract to CSV and remove
47
+
48
+ gfix undup -u user # Find duplicate contacts -> merger.json
49
+ gfix merge -u user # Merge duplicates locally (then use gcards push)
50
+ ```
51
+
52
+ ## Google People API Fields
53
+
54
+ ### Identity (Names)
55
+
56
+ | Field | Read/Write | Description |
57
+ |-------|------------|-------------|
58
+ | `names[].givenName` | Write | First name |
59
+ | `names[].familyName` | Write | Last name |
60
+ | `names[].middleName` | Write | Middle name |
61
+ | `names[].honorificPrefix` | Write | Prefix (Dr., Prof., etc.) |
62
+ | `names[].honorificSuffix` | Write | Suffix (Jr., III, PhD, etc.) |
63
+ | `names[].displayName` | Read-only | Full name (Google generates from above) |
64
+ | `names[].displayNameLastFirst` | Read-only | Sorting form "Last, First" (Google generates) |
65
+ | `names[].unstructuredName` | Write | Free-form name when structured fields not available |
66
+
67
+ ### Sorting
68
+
69
+ | Field | Read/Write | Description |
70
+ |-------|------------|-------------|
71
+ | `fileAses[].value` | Write | Custom sort override (e.g., "Home Depot" instead of "The Home Depot") |
72
+
73
+ If blank, Google sorts by `familyName` automatically.
74
+
75
+ ### Contact Information
76
+
77
+ | Field | Read/Write | Description |
78
+ |-------|------------|-------------|
79
+ | `emailAddresses[].value` | Write | Email address |
80
+ | `emailAddresses[].type` | Write | Label: home, work, other |
81
+ | `phoneNumbers[].value` | Write | Phone number |
82
+ | `phoneNumbers[].type` | Write | Label: mobile, work, home, main |
83
+
84
+ ### Business
85
+
86
+ | Field | Read/Write | Description |
87
+ |-------|------------|-------------|
88
+ | `organizations[].name` | Write | Company name |
89
+ | `organizations[].title` | Write | Job title |
90
+
91
+ ### Dates
92
+
93
+ | Field | Read/Write | Description |
94
+ |-------|------------|-------------|
95
+ | `birthdays[].date.year` | Write | Birth year (optional) |
96
+ | `birthdays[].date.month` | Write | Birth month (1-12) |
97
+ | `birthdays[].date.day` | Write | Birth day (1-31) |
98
+
99
+ ### Management (Required for Updates)
100
+
101
+ | Field | Read/Write | Description |
102
+ |-------|------------|-------------|
103
+ | `resourceName` | Read-only | Unique contact ID (e.g., `people/c12345`) |
104
+ | `etag` | Read-only | Version key - must match when updating |
105
+
106
+ The `etag` prevents conflicts: if Google's version changed since you downloaded, your update will fail. Run `gcards sync` to get the latest version.
107
+
108
+ ## Data Directory
109
+
110
+ - **Windows**: `%APPDATA%\gcards\` (app directory)
111
+ - **Linux/Mac**: `~/.config/gcards/` (app directory)
112
+ - **Development**: `./data/` symlink to app directory's `data/` folder
113
+
114
+ ### Directory Structure
115
+
116
+ ```
117
+ gcards/ # App directory (%APPDATA%\gcards or ~/.config/gcards)
118
+ config.json # Last user, settings
119
+ data/ # User data directory
120
+ <username>/
121
+ contacts/ # Active contacts (*.json)
122
+ deleted/ # Backup of deleted contact files
123
+ _delete/ # User requests to delete (move files here)
124
+ _add/ # User requests to add new contacts
125
+ fix-logs/ # Logs from gfix operations
126
+ index.json # Active contact index (hasPhoto, starred flags)
127
+ deleted.json # Deleted contacts index
128
+ notdeleted.json # Contacts skipped due to photo/starred
129
+ token.json # OAuth token (read-only)
130
+ token-write.json # OAuth token (read-write)
131
+ changes.log # Log from gfix names
132
+ birthdays.csv # Extracted birthdays
133
+ merger.json # Duplicates to merge (from gfix undup)
134
+ merged.json # Processed merges (history)
135
+ ```
136
+
137
+ ## Fixes Applied by `gfix inspect/apply`
138
+
139
+ - Remove leading/trailing dashes, quotes, spaces from name fields
140
+ - Extract repeated honorific prefixes (Dr., Prof.) to `honorificPrefix`
141
+ - Remove duplicate phone numbers (normalized comparison)
142
+ - Remove duplicate email addresses (case-insensitive)
143
+
144
+ ## Name Parsing (`gfix names`)
145
+
146
+ When a contact has a full name in `givenName` but no `familyName`:
147
+ - Parses "First Middle Last" into separate fields
148
+ - Updates `displayNameLastFirst` to proper "Last, First" format
149
+ - Cleans up `fileAses` entries (strips junk characters)
150
+ - Creates `changes.log` with all modifications
151
+
152
+ ## Workflow
153
+
154
+ 1. **Initial sync**: `gcards sync -u yourname`
155
+ 2. **Review fixes**: `gfix inspect -u yourname`
156
+ 3. **Apply fixes**: `gfix apply -u yourname`
157
+ 4. **Parse names**: `gfix names -u yourname --apply`
158
+ 5. **Push to Google**: `gcards push -u yourname`
159
+
160
+ ## Deleting Contacts
161
+
162
+ Three ways to mark contacts for deletion:
163
+
164
+ 1. **In index.json**: Add `"_delete": true` to an entry
165
+ 2. **In contact JSON**: Add `"_delete": true` to the contact file
166
+ 3. **Move to _delete/**: Move the contact file to the `_delete/` folder
167
+
168
+ Then run `gcards push` to apply deletions.
169
+
170
+ ### Photo/Starred Protection
171
+
172
+ Contacts with photos or starred status are **not deleted** from Google to prevent data loss:
173
+ - Contacts with non-default photos: `_delete` set to `"photo"`
174
+ - Starred/favorite contacts: `_delete` set to `"starred"`
175
+
176
+ These remain in Google and `index.json` with the skip reason. Review `notdeleted.json` after push.
177
+
178
+ ### Backup
179
+
180
+ All deleted contact files are moved to `deleted/` folder as backup (not permanently deleted).
181
+ Index entries move to `deleted.json`.
182
+
183
+ ## Duplicate Contact Merging
184
+
185
+ 1. **Find duplicates**: `gfix undup -u yourname`
186
+ - Creates `merger.json` with contacts having same name + overlapping email
187
+ 2. **Review**: Edit `merger.json`:
188
+ - Remove entries you don't want to merge
189
+ - Add `"_delete": true` to delete all contacts in a group instead of merging
190
+ 3. **Merge locally**: `gfix merge -u yourname`
191
+ - Updates target contact files with merged data
192
+ - Marks source contacts with `_delete: true`
193
+ - Moves processed entries to `merged.json`
194
+ 4. **Push to Google**: `gcards push -u yourname`
195
+ 5. **Resync**: `gcards sync -u yourname --full`
196
+
197
+ Example `merger.json`:
198
+ ```json
199
+ [
200
+ { "name": "John Smith", "emails": ["john@work.com"], "resourceNames": ["people/c1", "people/c2"] },
201
+ { "name": "Spam Contact", "emails": ["spam@x.com"], "resourceNames": ["people/c3", "people/c4"], "_delete": true }
202
+ ]
203
+ ```
204
+
205
+ ## ESC Key
206
+
207
+ Press ESC during sync/push to stop safely after the current operation.
208
+
209
+ ## Multiple Users
210
+
211
+ ```bash
212
+ # Process specific user
213
+ gcards sync -u alice
214
+
215
+ # Process all users
216
+ gcards sync -a
217
+ gcards push -a
218
+ ```
219
+
220
+ ## Setup
221
+
222
+ 1. Create Google Cloud project
223
+ 2. Enable People API
224
+ 3. Create OAuth2 credentials (Desktop app type)
225
+ 4. Download `credentials.json` to the gcards directory
226
+
227
+ ## License
228
+
229
+ MIT