@rashidazarang/airtable-mcp 1.6.0 → 2.1.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.
Files changed (116) hide show
  1. package/.github/ISSUE_TEMPLATE/bug-report.yml +173 -0
  2. package/.github/ISSUE_TEMPLATE/feature-request.yml +209 -0
  3. package/.github/ISSUE_TEMPLATE/security-report.yml +216 -0
  4. package/.github/pull_request_template.md +245 -0
  5. package/.github/workflows/ci-cd.yml +408 -0
  6. package/.github/workflows/security-audit.yml +316 -0
  7. package/API_DOCUMENTATION.md +897 -0
  8. package/CODE_OF_CONDUCT.md +181 -0
  9. package/Dockerfile.production +127 -0
  10. package/README.md +1 -0
  11. package/airtable-clipper/CHANGELOG.md +198 -0
  12. package/airtable-clipper/CHROME_STORE_SUBMISSION.md +343 -0
  13. package/airtable-clipper/LAUNCH_STRATEGY.md +495 -0
  14. package/airtable-clipper/LICENSE +21 -0
  15. package/airtable-clipper/OAUTH_SETUP.md +51 -0
  16. package/airtable-clipper/PRIVACY_POLICY.md +187 -0
  17. package/airtable-clipper/README.md +575 -0
  18. package/airtable-clipper/SUBMIT_TO_CHROME_STORE.md +273 -0
  19. package/airtable-clipper/build.sh +85 -0
  20. package/airtable-clipper/docs/QUICK_START.md +99 -0
  21. package/airtable-clipper/docs/SETUP.md +291 -0
  22. package/airtable-clipper/extension/background.js +337 -0
  23. package/airtable-clipper/extension/base-setup.html +324 -0
  24. package/airtable-clipper/extension/base-setup.js +471 -0
  25. package/airtable-clipper/extension/content.js +771 -0
  26. package/airtable-clipper/extension/icons/README.md +69 -0
  27. package/airtable-clipper/extension/icons/icon-16.png +3 -0
  28. package/airtable-clipper/extension/manifest.json +73 -0
  29. package/airtable-clipper/extension/popup.html +144 -0
  30. package/airtable-clipper/extension/popup.js +475 -0
  31. package/airtable-clipper/extension/styles/content.css +229 -0
  32. package/airtable-clipper/extension/styles/popup.css +477 -0
  33. package/airtable-clipper/privacy-policy.md +63 -0
  34. package/airtable-clipper/releases/v1.0.0/background.js +337 -0
  35. package/airtable-clipper/releases/v1.0.0/base-setup.html +324 -0
  36. package/airtable-clipper/releases/v1.0.0/base-setup.js +471 -0
  37. package/airtable-clipper/releases/v1.0.0/content.js +771 -0
  38. package/airtable-clipper/releases/v1.0.0/icons/README.md +69 -0
  39. package/airtable-clipper/releases/v1.0.0/icons/icon-128.png +2 -0
  40. package/airtable-clipper/releases/v1.0.0/icons/icon-16.png +3 -0
  41. package/airtable-clipper/releases/v1.0.0/icons/icon-32.png +2 -0
  42. package/airtable-clipper/releases/v1.0.0/icons/icon-48.png +2 -0
  43. package/airtable-clipper/releases/v1.0.0/manifest.json +73 -0
  44. package/airtable-clipper/releases/v1.0.0/popup.html +144 -0
  45. package/airtable-clipper/releases/v1.0.0/popup.js +475 -0
  46. package/airtable-clipper/releases/v1.0.0/sidepanel.html +25 -0
  47. package/airtable-clipper/releases/v1.0.0/styles/content.css +229 -0
  48. package/airtable-clipper/releases/v1.0.0/styles/popup.css +477 -0
  49. package/airtable-clipper/releases/v1.0.1/background.js +337 -0
  50. package/airtable-clipper/releases/v1.0.1/base-setup.html +324 -0
  51. package/airtable-clipper/releases/v1.0.1/base-setup.js +471 -0
  52. package/airtable-clipper/releases/v1.0.1/content.js +771 -0
  53. package/airtable-clipper/releases/v1.0.1/icons/README.md +69 -0
  54. package/airtable-clipper/releases/v1.0.1/icons/icon-128.png +2 -0
  55. package/airtable-clipper/releases/v1.0.1/icons/icon-16.png +3 -0
  56. package/airtable-clipper/releases/v1.0.1/icons/icon-32.png +2 -0
  57. package/airtable-clipper/releases/v1.0.1/icons/icon-48.png +2 -0
  58. package/airtable-clipper/releases/v1.0.1/manifest.json +70 -0
  59. package/airtable-clipper/releases/v1.0.1/popup.html +157 -0
  60. package/airtable-clipper/releases/v1.0.1/popup.js +562 -0
  61. package/airtable-clipper/releases/v1.0.1/sidepanel.html +25 -0
  62. package/airtable-clipper/releases/v1.0.1/styles/content.css +229 -0
  63. package/airtable-clipper/releases/v1.0.1/styles/popup.css +647 -0
  64. package/airtable-clipper/releases/v1.0.2/background.js +337 -0
  65. package/airtable-clipper/releases/v1.0.2/base-setup.html +324 -0
  66. package/airtable-clipper/releases/v1.0.2/base-setup.js +471 -0
  67. package/airtable-clipper/releases/v1.0.2/content.js +771 -0
  68. package/airtable-clipper/releases/v1.0.2/icons/README.md +69 -0
  69. package/airtable-clipper/releases/v1.0.2/icons/icon-128.png +2 -0
  70. package/airtable-clipper/releases/v1.0.2/icons/icon-16.png +3 -0
  71. package/airtable-clipper/releases/v1.0.2/icons/icon-32.png +2 -0
  72. package/airtable-clipper/releases/v1.0.2/icons/icon-48.png +2 -0
  73. package/airtable-clipper/releases/v1.0.2/manifest.json +62 -0
  74. package/airtable-clipper/releases/v1.0.2/popup.html +157 -0
  75. package/airtable-clipper/releases/v1.0.2/popup.js +567 -0
  76. package/airtable-clipper/releases/v1.0.2/sidepanel.html +25 -0
  77. package/airtable-clipper/releases/v1.0.2/styles/content.css +229 -0
  78. package/airtable-clipper/releases/v1.0.2/styles/popup.css +647 -0
  79. package/airtable-clipper/terms-of-service.md +124 -0
  80. package/airtable-clipper/test-credentials.md +61 -0
  81. package/airtable-clipper/test-extension/background.js +337 -0
  82. package/airtable-clipper/test-extension/base-setup.html +324 -0
  83. package/airtable-clipper/test-extension/base-setup.js +471 -0
  84. package/airtable-clipper/test-extension/content.js +873 -0
  85. package/airtable-clipper/test-extension/icons/README.md +69 -0
  86. package/airtable-clipper/test-extension/icons/icon-128.png +2 -0
  87. package/airtable-clipper/test-extension/icons/icon-16.png +3 -0
  88. package/airtable-clipper/test-extension/icons/icon-32.png +2 -0
  89. package/airtable-clipper/test-extension/icons/icon-48.png +2 -0
  90. package/airtable-clipper/test-extension/manifest.json +72 -0
  91. package/airtable-clipper/test-extension/popup.html +274 -0
  92. package/airtable-clipper/test-extension/popup.js +729 -0
  93. package/airtable-clipper/test-extension/sidepanel.html +25 -0
  94. package/airtable-clipper/test-extension/styles/content.css +229 -0
  95. package/airtable-clipper/test-extension/styles/popup.css +794 -0
  96. package/airtable_mcp_v2.js +1505 -0
  97. package/airtable_mcp_v2_oauth.js +1048 -0
  98. package/airtable_mcp_v3_advanced.js +1161 -0
  99. package/airtable_simple_production.js +532 -0
  100. package/docker-compose.production.yml +366 -0
  101. package/helm/airtable-mcp/Chart.yaml +122 -0
  102. package/helm/airtable-mcp/values.yaml +538 -0
  103. package/k8s/deployment.yaml +402 -0
  104. package/k8s/namespace.yaml +108 -0
  105. package/k8s/service.yaml +194 -0
  106. package/monitoring/alerts.yml +289 -0
  107. package/monitoring/prometheus.yml +224 -0
  108. package/package.json +6 -6
  109. package/.claude/settings.local.json +0 -12
  110. package/airtable-mcp-1.1.0.tgz +0 -0
  111. package/airtable_enhanced.js +0 -499
  112. package/airtable_simple_v1.2.4_backup.js +0 -277
  113. package/airtable_v1.4.0.js +0 -654
  114. package/rashidazarang-airtable-mcp-1.1.0.tgz +0 -0
  115. package/rashidazarang-airtable-mcp-1.2.0.tgz +0 -0
  116. package/rashidazarang-airtable-mcp-1.2.1.tgz +0 -0
@@ -0,0 +1,291 @@
1
+ # 🚀 Setup Guide - Airtable Clipper
2
+
3
+ This guide will walk you through setting up Airtable Clipper in under 5 minutes.
4
+
5
+ ## Prerequisites
6
+
7
+ Before you start, make sure you have:
8
+
9
+ - **Chrome Browser** (version 88 or higher)
10
+ - **Airtable Account** (free or paid)
11
+ - **Internet Connection** for Airtable API access
12
+
13
+ ## Step 1: Install the Extension
14
+
15
+ ### Option A: Chrome Web Store (Recommended)
16
+ 1. Visit the [Airtable Clipper Chrome Web Store page](https://chrome.google.com/webstore/detail/airtable-clipper/EXTENSION_ID)
17
+ 2. Click **"Add to Chrome"**
18
+ 3. Confirm by clicking **"Add Extension"**
19
+ 4. You'll see the Airtable Clipper icon (📎) in your Chrome toolbar
20
+
21
+ ### Option B: Manual Installation (Developers)
22
+ 1. Download the latest release from [GitHub](https://github.com/rashidazarang/airtable-clipper/releases)
23
+ 2. Unzip the downloaded file
24
+ 3. Open Chrome and go to `chrome://extensions/`
25
+ 4. Enable **"Developer mode"** (toggle in top-right)
26
+ 5. Click **"Load unpacked"** and select the `extension` folder
27
+ 6. The extension will appear in your toolbar
28
+
29
+ ## Step 2: Get Your Airtable Credentials
30
+
31
+ ### 2.1 Create a Personal Access Token
32
+
33
+ 1. **Go to Airtable Token Creation Page**
34
+ - Visit [airtable.com/create/tokens](https://airtable.com/create/tokens)
35
+ - Sign in to your Airtable account if prompted
36
+
37
+ 2. **Create New Token**
38
+ - Click **"Create new token"**
39
+ - **Name**: `Airtable Clipper` (or any name you prefer)
40
+ - **Description**: `Chrome extension for web clipping`
41
+
42
+ 3. **Add Required Scopes**
43
+ Select these permissions:
44
+ - ✅ `data.records:read` - Read records from your tables
45
+ - ✅ `data.records:write` - Create and update records
46
+ - ✅ `schema.bases:read` - Read table structures
47
+
48
+ 4. **Add Base Access**
49
+ - Click **"Add a base"**
50
+ - Select the base(s) you want to clip data to
51
+ - Choose **"All current and future tables in this base"**
52
+
53
+ 5. **Create Token**
54
+ - Click **"Create token"**
55
+ - **⚠️ Copy and save the token immediately** - it starts with `pat...`
56
+ - You won't be able to see it again!
57
+
58
+ ### 2.2 Get Your Base ID
59
+
60
+ 1. **Open Your Airtable Base**
61
+ - Go to [airtable.com](https://airtable.com)
62
+ - Open the base where you want to save clipped data
63
+
64
+ 2. **Copy Base ID from URL**
65
+ - Look at the URL in your browser address bar
66
+ - Find the part that looks like: `https://airtable.com/appXXXXXXXXXXXXXX/...`
67
+ - Copy the **Base ID** (the part starting with `app`)
68
+ - Example: `appTV04Fyu1Gvbunq`
69
+
70
+ ![Base ID Location](images/base-id-location.png)
71
+
72
+ ## Step 3: Configure the Extension
73
+
74
+ 1. **Open Extension Popup**
75
+ - Click the Airtable Clipper icon (📎) in your Chrome toolbar
76
+ - You should see the setup screen
77
+
78
+ 2. **Enter Your Credentials**
79
+ - **Personal Access Token**: Paste your token (starts with `pat...`)
80
+ - **Base ID**: Paste your base ID (starts with `app...`)
81
+
82
+ ![Setup Screen](images/setup-screen.png)
83
+
84
+ 3. **Test Connection**
85
+ - Click **"Connect to Airtable"**
86
+ - You should see "Connected successfully!" message
87
+ - The extension will load your table names automatically
88
+
89
+ 4. **Configure Tables (Optional)**
90
+ - **Default Table**: Where general web clips will be saved (default: "Clips")
91
+ - **LinkedIn Table**: Where LinkedIn profiles will be saved (default: "Contacts")
92
+ - You can change these later in settings
93
+
94
+ ## Step 4: Create Tables (If Needed)
95
+
96
+ The extension works best with properly structured tables. Here are recommended setups:
97
+
98
+ ### For LinkedIn Contacts (Recommended Table: "Contacts")
99
+
100
+ Create a table with these fields:
101
+
102
+ | Field Name | Field Type | Description |
103
+ |------------|------------|-------------|
104
+ | Name | Single line text | Contact's full name |
105
+ | Title | Single line text | Job title |
106
+ | Company | Single line text | Company name |
107
+ | Location | Single line text | Location/city |
108
+ | Email | Email | Email address (auto-guessed) |
109
+ | Phone | Phone number | Phone number |
110
+ | LinkedIn URL | URL | Link to LinkedIn profile |
111
+ | Date Added | Date | When contact was saved |
112
+ | Source | Single select | How contact was added |
113
+ | Lead Score | Number | Data quality score (0-100) |
114
+ | Notes | Long text | Additional information |
115
+ | Tags | Multiple select | Relevant tags |
116
+
117
+ ### For General Web Clips (Recommended Table: "Clips")
118
+
119
+ Create a table with these fields:
120
+
121
+ | Field Name | Field Type | Description |
122
+ |------------|------------|-------------|
123
+ | Title | Single line text | Page/article title |
124
+ | URL | URL | Source webpage URL |
125
+ | Content | Long text | Extracted content |
126
+ | Date Saved | Date | When content was clipped |
127
+ | Type | Single select | Content type (Article, Profile, etc.) |
128
+ | Domain | Single line text | Website domain |
129
+ | Tags | Multiple select | Auto-generated tags |
130
+ | Quality Score | Number | Extraction quality (0-100) |
131
+
132
+ ### Quick Table Creation
133
+
134
+ You can also let the extension suggest table structures:
135
+
136
+ 1. Try to save some content first
137
+ 2. If tables don't exist, the extension will show an error with suggested fields
138
+ 3. Create the tables manually with the suggested structure
139
+ 4. Try saving again
140
+
141
+ ## Step 5: Test the Extension
142
+
143
+ ### Test 1: Save a LinkedIn Profile
144
+
145
+ 1. **Visit a LinkedIn Profile**
146
+ - Go to any public LinkedIn profile (e.g., `linkedin.com/in/username`)
147
+ - Make sure you're not on the LinkedIn home feed
148
+
149
+ 2. **Save the Profile**
150
+ - Click the Airtable Clipper icon
151
+ - Click **"Save Profile"** or just **"Quick Save"**
152
+ - You should see a success notification
153
+
154
+ 3. **Check Airtable**
155
+ - Open your Airtable base
156
+ - Check the "Contacts" table (or your LinkedIn table)
157
+ - You should see the profile data saved
158
+
159
+ ### Test 2: Save a General Web Page
160
+
161
+ 1. **Visit Any Website**
162
+ - Go to an article, blog post, or any webpage
163
+ - Try a site like Medium, GitHub, or Stack Overflow
164
+
165
+ 2. **Save the Page**
166
+ - Click the Airtable Clipper icon
167
+ - Click **"Quick Save"**
168
+ - Wait for the success notification
169
+
170
+ 3. **Check Airtable**
171
+ - Check your "Clips" table (or default table)
172
+ - You should see the page data saved
173
+
174
+ ## Step 6: Explore Features
175
+
176
+ ### Floating Action Button
177
+ - Visit supported sites (LinkedIn, GitHub, etc.)
178
+ - Look for the floating 📎 button in the bottom-right corner
179
+ - Click it for quick saving
180
+
181
+ ### Right-Click Menu
182
+ - Right-click anywhere on a webpage
183
+ - Look for "Save to Airtable" in the context menu
184
+ - Great for saving selected text or specific content
185
+
186
+ ### Bulk Mode
187
+ - Hold **Shift** and click the extension icon
188
+ - Or use keyboard shortcut: **Cmd+Shift+B** (Mac) or **Ctrl+Shift+B** (Windows)
189
+ - Click multiple items on the page to select them
190
+ - Click **"Save Selected"** to save everything at once
191
+
192
+ ### Keyboard Shortcuts
193
+ - **Quick Save**: `Cmd+Shift+S` (Mac) or `Ctrl+Shift+S` (Windows)
194
+ - **Bulk Mode**: `Cmd+Shift+B` (Mac) or `Ctrl+Shift+B` (Windows)
195
+
196
+ ## Troubleshooting Common Issues
197
+
198
+ ### ❌ "Connection Failed" Error
199
+
200
+ **Check Your Token:**
201
+ - Make sure you copied the complete token (starts with `pat`)
202
+ - Token should be about 40-50 characters long
203
+ - Verify you have the required scopes enabled
204
+
205
+ **Check Your Base ID:**
206
+ - Make sure it starts with `app` (not `tbl` or other prefixes)
207
+ - Should be exactly 17 characters long
208
+ - Copy from the URL, not from the table ID
209
+
210
+ **Test Manually:**
211
+ ```bash
212
+ curl -H "Authorization: Bearer YOUR_TOKEN" \
213
+ https://api.airtable.com/v0/meta/bases/YOUR_BASE_ID/tables
214
+ ```
215
+
216
+ ### ❌ "Failed to Extract Data" Error
217
+
218
+ **Page Not Supported:**
219
+ - Try refreshing the page and trying again
220
+ - Some dynamic sites need a moment to load content
221
+ - Not all websites may be supported yet
222
+
223
+ **LinkedIn Specific:**
224
+ - Make sure you're on a profile page (`linkedin.com/in/username`)
225
+ - Some profiles may be private or have limited public data
226
+ - Try a different profile
227
+
228
+ ### ❌ "Table Not Found" Error
229
+
230
+ **Create Missing Tables:**
231
+ - The extension can't create tables automatically
232
+ - Create tables manually in Airtable with the suggested field names
233
+ - Make sure table names match exactly (case-sensitive)
234
+
235
+ ### 🐌 Slow Performance
236
+
237
+ **Browser Issues:**
238
+ - Close unused tabs to free up memory
239
+ - Disable other extensions temporarily
240
+ - Restart Chrome if it becomes sluggish
241
+
242
+ **Network Issues:**
243
+ - Check your internet connection
244
+ - Airtable API may be temporarily slow
245
+ - Try again in a few minutes
246
+
247
+ ## Advanced Configuration
248
+
249
+ ### Custom Field Mapping
250
+
251
+ You can customize how extracted data maps to your Airtable fields:
252
+
253
+ 1. Click the extension icon
254
+ 2. Click **"Settings"** at the bottom
255
+ 3. Modify the table and field mappings
256
+ 4. Changes are saved automatically
257
+
258
+ ### Automation Settings
259
+
260
+ - **Auto-Save LinkedIn**: Automatically save LinkedIn profiles when you visit them
261
+ - **Duplicate Detection**: Skip saving if a similar record already exists
262
+ - **Notifications**: Control when you see success/error messages
263
+
264
+ ### Privacy Settings
265
+
266
+ - **Data Processing**: All data processing happens locally in your browser
267
+ - **No Tracking**: The extension doesn't track your browsing or send data to external servers
268
+ - **Storage**: Settings are synced across your Chrome browsers via Chrome Sync
269
+
270
+ ## Getting Help
271
+
272
+ If you're still having issues:
273
+
274
+ 1. **Check the Documentation**: [Full docs](../README.md)
275
+ 2. **Search Issues**: [GitHub Issues](https://github.com/rashidazarang/airtable-clipper/issues)
276
+ 3. **Ask for Help**: [GitHub Discussions](https://github.com/rashidazarang/airtable-clipper/discussions)
277
+ 4. **Report Bugs**: [Create a new issue](https://github.com/rashidazarang/airtable-clipper/issues/new)
278
+
279
+ Include this information when asking for help:
280
+ - Chrome version
281
+ - Extension version
282
+ - Website you're trying to clip from
283
+ - Error message (if any)
284
+ - Steps to reproduce the issue
285
+
286
+ ---
287
+
288
+ **Next Steps:**
289
+ - [Learn advanced features](ADVANCED.md)
290
+ - [See use case examples](EXAMPLES.md)
291
+ - [Contribute to the project](../README.md#contributing)
@@ -0,0 +1,337 @@
1
+ // Airtable Clipper - Background Service Worker
2
+ // Handles context menus, notifications, and cross-tab communication
3
+
4
+ class BackgroundService {
5
+ constructor() {
6
+ this.init();
7
+ }
8
+
9
+ init() {
10
+ // Create context menu on installation
11
+ chrome.runtime.onInstalled.addListener(() => {
12
+ this.createContextMenus();
13
+ this.setupNotifications();
14
+ });
15
+
16
+ // Handle context menu clicks
17
+ chrome.contextMenus.onClicked.addListener((info, tab) => {
18
+ this.handleContextMenuClick(info, tab);
19
+ });
20
+
21
+ // Handle messages from content scripts and popup
22
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
23
+ this.handleMessage(message, sender, sendResponse);
24
+ return true; // Keep message channel open for async responses
25
+ });
26
+
27
+ // Handle extension icon clicks
28
+ chrome.action.onClicked.addListener((tab) => {
29
+ this.handleActionClick(tab);
30
+ });
31
+
32
+ // Periodic cleanup
33
+ chrome.alarms.onAlarm.addListener((alarm) => {
34
+ if (alarm.name === 'cleanup') {
35
+ this.cleanupStorage();
36
+ }
37
+ });
38
+
39
+ // Set up cleanup alarm
40
+ chrome.alarms.create('cleanup', { delayInMinutes: 60, periodInMinutes: 60 });
41
+ }
42
+
43
+ createContextMenus() {
44
+ // Remove existing menus
45
+ chrome.contextMenus.removeAll();
46
+
47
+ // Main menu
48
+ chrome.contextMenus.create({
49
+ id: 'save-to-airtable',
50
+ title: 'Save to Airtable',
51
+ contexts: ['page', 'selection', 'link', 'image']
52
+ });
53
+
54
+ // LinkedIn specific
55
+ chrome.contextMenus.create({
56
+ id: 'save-linkedin-profile',
57
+ title: 'Save LinkedIn Profile',
58
+ contexts: ['page'],
59
+ documentUrlPatterns: ['*://www.linkedin.com/in/*', '*://linkedin.com/in/*']
60
+ });
61
+
62
+ // Quick save selection
63
+ chrome.contextMenus.create({
64
+ id: 'quick-save-selection',
65
+ title: 'Quick Save Selected Text',
66
+ contexts: ['selection']
67
+ });
68
+ }
69
+
70
+ async handleContextMenuClick(info, tab) {
71
+ try {
72
+ switch (info.menuItemId) {
73
+ case 'save-to-airtable':
74
+ await this.triggerSave(tab, { type: 'general', info });
75
+ break;
76
+ case 'save-linkedin-profile':
77
+ await this.triggerSave(tab, { type: 'linkedin', info });
78
+ break;
79
+ case 'quick-save-selection':
80
+ await this.triggerSave(tab, { type: 'selection', text: info.selectionText });
81
+ break;
82
+ }
83
+ } catch (error) {
84
+ console.error('Context menu error:', error);
85
+ this.showErrorNotification(error.message);
86
+ }
87
+ }
88
+
89
+ async triggerSave(tab, data) {
90
+ // Send message to content script to initiate save
91
+ try {
92
+ const response = await chrome.tabs.sendMessage(tab.id, {
93
+ action: 'triggerSave',
94
+ data: data
95
+ });
96
+
97
+ if (response && response.success) {
98
+ this.showSuccessNotification('Content saved to Airtable!');
99
+ } else {
100
+ throw new Error(response?.error || 'Failed to save content');
101
+ }
102
+ } catch (error) {
103
+ console.error('Save trigger error:', error);
104
+ // Fallback: open popup if content script isn't available
105
+ chrome.action.openPopup();
106
+ }
107
+ }
108
+
109
+ async handleMessage(message, sender, sendResponse) {
110
+ try {
111
+ switch (message.action) {
112
+ case 'saveToAirtable':
113
+ const result = await this.saveToAirtable(message.data);
114
+ sendResponse({ success: true, result });
115
+ break;
116
+
117
+ case 'getSettings':
118
+ const settings = await this.getSettings();
119
+ sendResponse({ success: true, settings });
120
+ break;
121
+
122
+ case 'saveSettings':
123
+ await this.saveSettings(message.settings);
124
+ sendResponse({ success: true });
125
+ break;
126
+
127
+ case 'showNotification':
128
+ this.showNotification(message.title, message.message, message.type);
129
+ sendResponse({ success: true });
130
+ break;
131
+
132
+ case 'openSidePanel':
133
+ if (sender.tab) {
134
+ chrome.sidePanel.open({ tabId: sender.tab.id });
135
+ }
136
+ sendResponse({ success: true });
137
+ break;
138
+
139
+ default:
140
+ sendResponse({ success: false, error: 'Unknown action' });
141
+ }
142
+ } catch (error) {
143
+ console.error('Message handler error:', error);
144
+ sendResponse({ success: false, error: error.message });
145
+ }
146
+ }
147
+
148
+ async saveToAirtable(data) {
149
+ // Get user settings
150
+ const settings = await this.getSettings();
151
+
152
+ if (!settings.airtableToken || !settings.baseId) {
153
+ throw new Error('Airtable credentials not configured');
154
+ }
155
+
156
+ // Import Airtable client
157
+ const { AirtableClient } = await import('./lib/airtable-client.js');
158
+ const client = new AirtableClient(settings.airtableToken, settings.baseId);
159
+
160
+ // Process and save data
161
+ const processedData = await this.processData(data, settings);
162
+ const result = await client.createRecord(processedData.table, processedData.fields);
163
+
164
+ // Update usage stats
165
+ await this.updateUsageStats();
166
+
167
+ return result;
168
+ }
169
+
170
+ async processData(data, settings) {
171
+ // Smart data processing based on content type
172
+ if (data.type === 'linkedin') {
173
+ return {
174
+ table: settings.linkedinTable || 'Contacts',
175
+ fields: this.processLinkedInData(data)
176
+ };
177
+ } else if (data.type === 'selection') {
178
+ return {
179
+ table: settings.defaultTable || 'Clips',
180
+ fields: {
181
+ 'Content': data.text,
182
+ 'Source URL': data.url,
183
+ 'Date Saved': new Date().toISOString(),
184
+ 'Type': 'Text Selection'
185
+ }
186
+ };
187
+ } else {
188
+ return {
189
+ table: settings.defaultTable || 'Clips',
190
+ fields: {
191
+ 'Title': data.title || 'Untitled',
192
+ 'URL': data.url,
193
+ 'Content': data.content,
194
+ 'Date Saved': new Date().toISOString(),
195
+ 'Type': 'Web Page'
196
+ }
197
+ };
198
+ }
199
+ }
200
+
201
+ processLinkedInData(data) {
202
+ return {
203
+ 'Name': data.name || '',
204
+ 'Title': data.title || '',
205
+ 'Company': data.company || '',
206
+ 'Location': data.location || '',
207
+ 'LinkedIn URL': data.url || '',
208
+ 'Date Added': new Date().toISOString(),
209
+ 'Source': 'LinkedIn',
210
+ 'Notes': data.about ? data.about.substring(0, 500) + '...' : ''
211
+ };
212
+ }
213
+
214
+ async getSettings() {
215
+ const result = await chrome.storage.sync.get([
216
+ 'airtableToken',
217
+ 'baseId',
218
+ 'linkedinTable',
219
+ 'defaultTable',
220
+ 'autoSave',
221
+ 'notifications'
222
+ ]);
223
+
224
+ return {
225
+ airtableToken: result.airtableToken || '',
226
+ baseId: result.baseId || '',
227
+ linkedinTable: result.linkedinTable || 'Contacts',
228
+ defaultTable: result.defaultTable || 'Clips',
229
+ autoSave: result.autoSave || false,
230
+ notifications: result.notifications !== false // Default to true
231
+ };
232
+ }
233
+
234
+ async saveSettings(settings) {
235
+ await chrome.storage.sync.set(settings);
236
+ }
237
+
238
+ async updateUsageStats() {
239
+ const stats = await chrome.storage.local.get(['usageStats']);
240
+ const currentStats = stats.usageStats || {
241
+ totalClips: 0,
242
+ weeklyClips: 0,
243
+ lastResetDate: new Date().toDateString()
244
+ };
245
+
246
+ // Reset weekly counter if it's a new week
247
+ const today = new Date().toDateString();
248
+ if (currentStats.lastResetDate !== today) {
249
+ const lastReset = new Date(currentStats.lastResetDate);
250
+ const daysSinceReset = Math.floor((Date.now() - lastReset.getTime()) / (1000 * 60 * 60 * 24));
251
+
252
+ if (daysSinceReset >= 7) {
253
+ currentStats.weeklyClips = 0;
254
+ currentStats.lastResetDate = today;
255
+ }
256
+ }
257
+
258
+ currentStats.totalClips++;
259
+ currentStats.weeklyClips++;
260
+
261
+ await chrome.storage.local.set({ usageStats: currentStats });
262
+ }
263
+
264
+ setupNotifications() {
265
+ // Check if notifications are supported
266
+ if ('notifications' in chrome) {
267
+ // Request notification permission if needed
268
+ chrome.notifications.getPermissionLevel((level) => {
269
+ if (level !== 'granted') {
270
+ console.log('Notification permission not granted');
271
+ }
272
+ });
273
+ }
274
+ }
275
+
276
+ showSuccessNotification(message) {
277
+ this.showNotification('Success!', message, 'success');
278
+ }
279
+
280
+ showErrorNotification(message) {
281
+ this.showNotification('Error', message, 'error');
282
+ }
283
+
284
+ async showNotification(title, message, type = 'basic') {
285
+ const settings = await this.getSettings();
286
+ if (!settings.notifications) return;
287
+
288
+ const iconUrl = type === 'error' ? 'icons/icon-error-48.png' : 'icons/icon-48.png';
289
+
290
+ chrome.notifications.create({
291
+ type: 'basic',
292
+ iconUrl: iconUrl,
293
+ title: title,
294
+ message: message
295
+ });
296
+ }
297
+
298
+ async handleActionClick(tab) {
299
+ // Check if this is a LinkedIn profile page
300
+ if (tab.url.includes('linkedin.com/in/')) {
301
+ // Try to extract LinkedIn data automatically
302
+ try {
303
+ await this.triggerSave(tab, { type: 'linkedin' });
304
+ } catch (error) {
305
+ // Fallback to opening popup
306
+ chrome.action.openPopup();
307
+ }
308
+ } else {
309
+ // Open popup for general pages
310
+ chrome.action.openPopup();
311
+ }
312
+ }
313
+
314
+ async cleanupStorage() {
315
+ // Clean up old temporary data
316
+ const result = await chrome.storage.local.get(null);
317
+ const keysToRemove = [];
318
+
319
+ for (const [key, value] of Object.entries(result)) {
320
+ // Remove temporary data older than 24 hours
321
+ if (key.startsWith('temp_') && value.timestamp) {
322
+ const age = Date.now() - value.timestamp;
323
+ if (age > 24 * 60 * 60 * 1000) { // 24 hours
324
+ keysToRemove.push(key);
325
+ }
326
+ }
327
+ }
328
+
329
+ if (keysToRemove.length > 0) {
330
+ await chrome.storage.local.remove(keysToRemove);
331
+ console.log(`Cleaned up ${keysToRemove.length} temporary items`);
332
+ }
333
+ }
334
+ }
335
+
336
+ // Initialize the background service
337
+ new BackgroundService();