@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,124 @@
1
+ # Terms of Service for Airtable Clipper
2
+
3
+ **Last updated: August 15, 2025**
4
+
5
+ ## 1. Acceptance of Terms
6
+
7
+ By installing, accessing, or using Airtable Clipper ("the Extension"), you agree to be bound by these Terms of Service ("Terms"). If you do not agree to these Terms, do not use the Extension.
8
+
9
+ ## 2. Description of Service
10
+
11
+ Airtable Clipper is a Chrome browser extension that enables users to save web content directly to their personal Airtable bases. The Extension provides functionality to:
12
+
13
+ - Save webpage titles, URLs, and content to Airtable
14
+ - Extract LinkedIn profile information (public data only)
15
+ - Organize web research and data collection
16
+ - Bulk selection and saving of multiple items
17
+
18
+ ## 3. User Responsibilities
19
+
20
+ ### 3.1 Account Security
21
+ - You are responsible for maintaining the security of your Airtable credentials
22
+ - You must not share your Personal Access Token with others
23
+ - You agree to notify us immediately of any unauthorized use of your account
24
+
25
+ ### 3.2 Lawful Use
26
+ - You agree to use the Extension only for lawful purposes
27
+ - You will not use the Extension to collect or process data in violation of applicable laws
28
+ - You respect the terms of service of websites you visit while using the Extension
29
+
30
+ ### 3.3 Data Accuracy
31
+ - You are responsible for the accuracy of data you save to your Airtable bases
32
+ - You understand that the Extension processes publicly available web content only
33
+
34
+ ## 4. Privacy and Data Protection
35
+
36
+ - Your data privacy is governed by our Privacy Policy
37
+ - We do not store or access your Airtable data
38
+ - All data processing occurs locally in your browser
39
+ - We do not sell or share your personal information
40
+
41
+ ## 5. Intellectual Property
42
+
43
+ ### 5.1 Extension Rights
44
+ - The Extension and its original content are owned by Rashid Azarang
45
+ - You are granted a limited, non-exclusive license to use the Extension
46
+
47
+ ### 5.2 User Content
48
+ - You retain ownership of all data you save using the Extension
49
+ - You grant us no rights to your data or content
50
+
51
+ ## 6. Third-Party Services
52
+
53
+ ### 6.1 Airtable Integration
54
+ - The Extension integrates with Airtable's services
55
+ - Your use of Airtable is subject to Airtable's Terms of Service
56
+ - We are not responsible for Airtable's availability or performance
57
+
58
+ ### 6.2 Website Content
59
+ - You are responsible for respecting the terms of service of websites you visit
60
+ - We do not control or endorse third-party website content
61
+
62
+ ## 7. Disclaimers
63
+
64
+ ### 7.1 Service Availability
65
+ - The Extension is provided "as is" without warranties of any kind
66
+ - We do not guarantee uninterrupted or error-free service
67
+ - We reserve the right to modify or discontinue the Extension at any time
68
+
69
+ ### 7.2 Data Loss
70
+ - While we strive for reliability, we cannot guarantee against data loss
71
+ - You are responsible for backing up important data
72
+ - We recommend regular exports of your Airtable data
73
+
74
+ ## 8. Limitation of Liability
75
+
76
+ To the maximum extent permitted by law:
77
+ - We shall not be liable for any indirect, incidental, or consequential damages
78
+ - Our total liability shall not exceed $10 USD
79
+ - We are not liable for data loss, business interruption, or lost profits
80
+
81
+ ## 9. Support and Updates
82
+
83
+ ### 9.1 Support
84
+ - Support is provided on a best-effort basis through GitHub issues
85
+ - We do not guarantee response times or issue resolution
86
+
87
+ ### 9.2 Updates
88
+ - We may release updates to improve functionality and security
89
+ - Updates are provided at our discretion
90
+ - Continued use after updates constitutes acceptance of changes
91
+
92
+ ## 10. Termination
93
+
94
+ ### 10.1 User Termination
95
+ - You may stop using the Extension at any time by uninstalling it
96
+ - You may disconnect your Airtable account through the Extension settings
97
+
98
+ ### 10.2 Service Termination
99
+ - We may terminate or suspend the Extension for violations of these Terms
100
+ - We may discontinue the Extension with reasonable notice
101
+
102
+ ## 11. Changes to Terms
103
+
104
+ - We may update these Terms from time to time
105
+ - Changes will be posted on this page with an updated date
106
+ - Continued use after changes constitutes acceptance of new Terms
107
+
108
+ ## 12. Governing Law
109
+
110
+ These Terms are governed by the laws of the United States and the State of California, without regard to conflict of law principles.
111
+
112
+ ## 13. Contact Information
113
+
114
+ For questions about these Terms, please contact:
115
+ - Email: rashid.azarang.e@gmail.com
116
+ - GitHub: https://github.com/rashidazarang/airtable-clipper/issues
117
+
118
+ ## 14. Severability
119
+
120
+ If any provision of these Terms is found to be unenforceable, the remaining provisions will remain in full force and effect.
121
+
122
+ ---
123
+
124
+ By using Airtable Clipper, you acknowledge that you have read, understood, and agree to be bound by these Terms of Service.
@@ -0,0 +1,61 @@
1
+ # Test Credentials for Airtable Clipper
2
+
3
+ ## For Manual Connection Testing
4
+
5
+ ### Personal Access Token:
6
+ ```
7
+ pat_yourTokenHere_generateFromAirtable
8
+ ```
9
+ *Note: You need to create this from https://airtable.com/create/tokens*
10
+
11
+ ### Base ID:
12
+ ```
13
+ appYourBaseId
14
+ ```
15
+ *Note: This is found in your Airtable URL like https://airtable.com/appYourBaseId/...*
16
+
17
+ ## Setting Up Test Base
18
+
19
+ 1. Go to https://airtable.com
20
+ 2. Create a new base called "Clipper Test"
21
+ 3. Create these tables:
22
+
23
+ ### Table 1: "Contacts"
24
+ Fields:
25
+ - Name (Single line text)
26
+ - Title (Single line text)
27
+ - Company (Single line text)
28
+ - LinkedIn URL (URL)
29
+ - Location (Single line text)
30
+ - Date Added (Date)
31
+ - Source (Single select: LinkedIn, Manual, Import)
32
+ - Notes (Long text)
33
+
34
+ ### Table 2: "Web Clips"
35
+ Fields:
36
+ - Title (Single line text)
37
+ - URL (URL)
38
+ - Content (Long text)
39
+ - Domain (Single line text)
40
+ - Saved At (Date)
41
+ - Type (Single select: Web Page, Text Selection, Image)
42
+
43
+ ## OAuth Client ID (Already Configured)
44
+ ```
45
+ db9710e0-7d4b-4be6-bb15-2621448b0425
46
+ ```
47
+
48
+ ## Testing Steps
49
+
50
+ 1. **Install Extension**: Load the `test-extension` folder as an unpacked extension in Chrome
51
+ 2. **Manual Connection**: Use the Personal Access Token and Base ID from above
52
+ 3. **Test Quick Save**: Visit any webpage and click "Save Page"
53
+ 4. **Test LinkedIn**: Visit a LinkedIn profile and click "Save Profile"
54
+ 5. **Test Bulk Mode**: Try bulk selection on a webpage
55
+
56
+ ## Troubleshooting
57
+
58
+ - Check Chrome DevTools Console for errors
59
+ - Verify token has correct permissions (data.records:read, data.records:write, schema.bases:read)
60
+ - Ensure base ID is correct from Airtable URL
61
+ - Check that table names match exactly (case-sensitive)
@@ -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
+ 'Saved At': new Date().toISOString().split('T')[0],
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
+ 'Saved At': new Date().toISOString().split('T')[0],
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().split('T')[0],
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();