@rashidazarang/airtable-mcp 1.5.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 (119) 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 +55 -10
  11. package/RELEASE_NOTES_v1.6.0.md +248 -0
  12. package/airtable-clipper/CHANGELOG.md +198 -0
  13. package/airtable-clipper/CHROME_STORE_SUBMISSION.md +343 -0
  14. package/airtable-clipper/LAUNCH_STRATEGY.md +495 -0
  15. package/airtable-clipper/LICENSE +21 -0
  16. package/airtable-clipper/OAUTH_SETUP.md +51 -0
  17. package/airtable-clipper/PRIVACY_POLICY.md +187 -0
  18. package/airtable-clipper/README.md +575 -0
  19. package/airtable-clipper/SUBMIT_TO_CHROME_STORE.md +273 -0
  20. package/airtable-clipper/build.sh +85 -0
  21. package/airtable-clipper/docs/QUICK_START.md +99 -0
  22. package/airtable-clipper/docs/SETUP.md +291 -0
  23. package/airtable-clipper/extension/background.js +337 -0
  24. package/airtable-clipper/extension/base-setup.html +324 -0
  25. package/airtable-clipper/extension/base-setup.js +471 -0
  26. package/airtable-clipper/extension/content.js +771 -0
  27. package/airtable-clipper/extension/icons/README.md +69 -0
  28. package/airtable-clipper/extension/icons/icon-16.png +3 -0
  29. package/airtable-clipper/extension/manifest.json +73 -0
  30. package/airtable-clipper/extension/popup.html +144 -0
  31. package/airtable-clipper/extension/popup.js +475 -0
  32. package/airtable-clipper/extension/styles/content.css +229 -0
  33. package/airtable-clipper/extension/styles/popup.css +477 -0
  34. package/airtable-clipper/privacy-policy.md +63 -0
  35. package/airtable-clipper/releases/v1.0.0/background.js +337 -0
  36. package/airtable-clipper/releases/v1.0.0/base-setup.html +324 -0
  37. package/airtable-clipper/releases/v1.0.0/base-setup.js +471 -0
  38. package/airtable-clipper/releases/v1.0.0/content.js +771 -0
  39. package/airtable-clipper/releases/v1.0.0/icons/README.md +69 -0
  40. package/airtable-clipper/releases/v1.0.0/icons/icon-128.png +2 -0
  41. package/airtable-clipper/releases/v1.0.0/icons/icon-16.png +3 -0
  42. package/airtable-clipper/releases/v1.0.0/icons/icon-32.png +2 -0
  43. package/airtable-clipper/releases/v1.0.0/icons/icon-48.png +2 -0
  44. package/airtable-clipper/releases/v1.0.0/manifest.json +73 -0
  45. package/airtable-clipper/releases/v1.0.0/popup.html +144 -0
  46. package/airtable-clipper/releases/v1.0.0/popup.js +475 -0
  47. package/airtable-clipper/releases/v1.0.0/sidepanel.html +25 -0
  48. package/airtable-clipper/releases/v1.0.0/styles/content.css +229 -0
  49. package/airtable-clipper/releases/v1.0.0/styles/popup.css +477 -0
  50. package/airtable-clipper/releases/v1.0.1/background.js +337 -0
  51. package/airtable-clipper/releases/v1.0.1/base-setup.html +324 -0
  52. package/airtable-clipper/releases/v1.0.1/base-setup.js +471 -0
  53. package/airtable-clipper/releases/v1.0.1/content.js +771 -0
  54. package/airtable-clipper/releases/v1.0.1/icons/README.md +69 -0
  55. package/airtable-clipper/releases/v1.0.1/icons/icon-128.png +2 -0
  56. package/airtable-clipper/releases/v1.0.1/icons/icon-16.png +3 -0
  57. package/airtable-clipper/releases/v1.0.1/icons/icon-32.png +2 -0
  58. package/airtable-clipper/releases/v1.0.1/icons/icon-48.png +2 -0
  59. package/airtable-clipper/releases/v1.0.1/manifest.json +70 -0
  60. package/airtable-clipper/releases/v1.0.1/popup.html +157 -0
  61. package/airtable-clipper/releases/v1.0.1/popup.js +562 -0
  62. package/airtable-clipper/releases/v1.0.1/sidepanel.html +25 -0
  63. package/airtable-clipper/releases/v1.0.1/styles/content.css +229 -0
  64. package/airtable-clipper/releases/v1.0.1/styles/popup.css +647 -0
  65. package/airtable-clipper/releases/v1.0.2/background.js +337 -0
  66. package/airtable-clipper/releases/v1.0.2/base-setup.html +324 -0
  67. package/airtable-clipper/releases/v1.0.2/base-setup.js +471 -0
  68. package/airtable-clipper/releases/v1.0.2/content.js +771 -0
  69. package/airtable-clipper/releases/v1.0.2/icons/README.md +69 -0
  70. package/airtable-clipper/releases/v1.0.2/icons/icon-128.png +2 -0
  71. package/airtable-clipper/releases/v1.0.2/icons/icon-16.png +3 -0
  72. package/airtable-clipper/releases/v1.0.2/icons/icon-32.png +2 -0
  73. package/airtable-clipper/releases/v1.0.2/icons/icon-48.png +2 -0
  74. package/airtable-clipper/releases/v1.0.2/manifest.json +62 -0
  75. package/airtable-clipper/releases/v1.0.2/popup.html +157 -0
  76. package/airtable-clipper/releases/v1.0.2/popup.js +567 -0
  77. package/airtable-clipper/releases/v1.0.2/sidepanel.html +25 -0
  78. package/airtable-clipper/releases/v1.0.2/styles/content.css +229 -0
  79. package/airtable-clipper/releases/v1.0.2/styles/popup.css +647 -0
  80. package/airtable-clipper/terms-of-service.md +124 -0
  81. package/airtable-clipper/test-credentials.md +61 -0
  82. package/airtable-clipper/test-extension/background.js +337 -0
  83. package/airtable-clipper/test-extension/base-setup.html +324 -0
  84. package/airtable-clipper/test-extension/base-setup.js +471 -0
  85. package/airtable-clipper/test-extension/content.js +873 -0
  86. package/airtable-clipper/test-extension/icons/README.md +69 -0
  87. package/airtable-clipper/test-extension/icons/icon-128.png +2 -0
  88. package/airtable-clipper/test-extension/icons/icon-16.png +3 -0
  89. package/airtable-clipper/test-extension/icons/icon-32.png +2 -0
  90. package/airtable-clipper/test-extension/icons/icon-48.png +2 -0
  91. package/airtable-clipper/test-extension/manifest.json +72 -0
  92. package/airtable-clipper/test-extension/popup.html +274 -0
  93. package/airtable-clipper/test-extension/popup.js +729 -0
  94. package/airtable-clipper/test-extension/sidepanel.html +25 -0
  95. package/airtable-clipper/test-extension/styles/content.css +229 -0
  96. package/airtable-clipper/test-extension/styles/popup.css +794 -0
  97. package/airtable_mcp_v2.js +1505 -0
  98. package/airtable_mcp_v2_oauth.js +1048 -0
  99. package/airtable_mcp_v3_advanced.js +1161 -0
  100. package/airtable_simple.js +447 -1
  101. package/airtable_simple_production.js +532 -0
  102. package/docker-compose.production.yml +366 -0
  103. package/helm/airtable-mcp/Chart.yaml +122 -0
  104. package/helm/airtable-mcp/values.yaml +538 -0
  105. package/k8s/deployment.yaml +402 -0
  106. package/k8s/namespace.yaml +108 -0
  107. package/k8s/service.yaml +194 -0
  108. package/monitoring/alerts.yml +289 -0
  109. package/monitoring/prometheus.yml +224 -0
  110. package/package.json +6 -6
  111. package/test_v1.6.0_comprehensive.sh +187 -0
  112. package/.claude/settings.local.json +0 -12
  113. package/airtable-mcp-1.1.0.tgz +0 -0
  114. package/airtable_enhanced.js +0 -499
  115. package/airtable_simple_v1.2.4_backup.js +0 -277
  116. package/airtable_v1.4.0.js +0 -654
  117. package/rashidazarang-airtable-mcp-1.1.0.tgz +0 -0
  118. package/rashidazarang-airtable-mcp-1.2.0.tgz +0 -0
  119. package/rashidazarang-airtable-mcp-1.2.1.tgz +0 -0
@@ -0,0 +1,471 @@
1
+ // Airtable Clipper Base Setup Wizard
2
+ import { BaseTemplate } from './lib/base-template.js';
3
+ import { AirtableClient } from './lib/airtable-client.js';
4
+
5
+ class BaseSetupWizard {
6
+ constructor() {
7
+ this.currentStep = 1;
8
+ this.maxStep = 4;
9
+ this.setupMethod = 'template';
10
+ this.template = null;
11
+ this.baseTemplate = new BaseTemplate();
12
+ this.init();
13
+ }
14
+
15
+ init() {
16
+ this.bindEvents();
17
+ this.updateStepIndicator();
18
+ }
19
+
20
+ bindEvents() {
21
+ // Setup method radio buttons
22
+ document.querySelectorAll('input[name="setupMethod"]').forEach(radio => {
23
+ radio.addEventListener('change', (e) => {
24
+ this.setupMethod = e.target.value;
25
+ this.updateStep2Content();
26
+ });
27
+ });
28
+
29
+ // Table checkboxes
30
+ document.querySelectorAll('#step2 input[type="checkbox"]').forEach(checkbox => {
31
+ checkbox.addEventListener('change', () => {
32
+ this.updatePreview();
33
+ });
34
+ });
35
+
36
+ // Base name input
37
+ document.getElementById('baseName').addEventListener('input', () => {
38
+ this.updatePreview();
39
+ });
40
+ }
41
+
42
+ updateStep2Content() {
43
+ const step2 = document.getElementById('step2');
44
+ const step3NextBtn = document.getElementById('step3NextBtn');
45
+
46
+ if (this.setupMethod === 'existing') {
47
+ step2.querySelector('h2').textContent = '🔍 Validate Existing Base';
48
+ step2.querySelector('p').textContent = 'We\'ll check your current Airtable base structure and suggest improvements.';
49
+ step3NextBtn.textContent = 'Validate Base';
50
+ } else {
51
+ step2.querySelector('h2').textContent = '📋 Configure Your Database';
52
+ step2.querySelector('p').textContent = 'Customize which tables to include based on your use case:';
53
+ step3NextBtn.textContent = 'Create Database';
54
+ }
55
+ }
56
+
57
+ async nextStep() {
58
+ if (this.currentStep < this.maxStep) {
59
+ this.currentStep++;
60
+ this.updateStepDisplay();
61
+
62
+ if (this.currentStep === 3) {
63
+ await this.generatePreview();
64
+ } else if (this.currentStep === 4) {
65
+ await this.prepareValidation();
66
+ }
67
+ }
68
+ }
69
+
70
+ prevStep() {
71
+ if (this.currentStep > 1) {
72
+ this.currentStep--;
73
+ this.updateStepDisplay();
74
+ }
75
+ }
76
+
77
+ updateStepDisplay() {
78
+ // Hide all steps
79
+ document.querySelectorAll('.wizard-step').forEach(step => {
80
+ step.classList.remove('active');
81
+ });
82
+
83
+ // Show current step
84
+ document.getElementById(`step${this.currentStep}`).classList.add('active');
85
+
86
+ // Update step indicator
87
+ this.updateStepIndicator();
88
+ }
89
+
90
+ updateStepIndicator() {
91
+ document.querySelectorAll('.step-dot').forEach((dot, index) => {
92
+ dot.classList.remove('active', 'completed');
93
+
94
+ if (index + 1 < this.currentStep) {
95
+ dot.classList.add('completed');
96
+ } else if (index + 1 === this.currentStep) {
97
+ dot.classList.add('active');
98
+ }
99
+ });
100
+ }
101
+
102
+ async generatePreview() {
103
+ this.showLoading('Generating template...');
104
+
105
+ try {
106
+ const config = this.getConfiguration();
107
+ this.template = await this.baseTemplate.createOptimalBase(config);
108
+
109
+ if (this.template.success) {
110
+ this.displayTemplatePreview();
111
+
112
+ if (this.setupMethod === 'template') {
113
+ this.showTemplateLink();
114
+ } else {
115
+ this.showManualInstructions();
116
+ }
117
+ } else {
118
+ throw new Error(this.template.error || 'Failed to generate template');
119
+ }
120
+ } catch (error) {
121
+ console.error('Template generation failed:', error);
122
+ this.showError('Failed to generate template: ' + error.message);
123
+ } finally {
124
+ this.hideLoading();
125
+ }
126
+ }
127
+
128
+ getConfiguration() {
129
+ return {
130
+ baseName: document.getElementById('baseName').value || 'Airtable Clipper Database',
131
+ includeContacts: document.getElementById('includeContacts').checked,
132
+ includeClips: document.getElementById('includeClips').checked,
133
+ includeCompanies: document.getElementById('includeCompanies').checked,
134
+ includeTasks: document.getElementById('includeTasks').checked
135
+ };
136
+ }
137
+
138
+ displayTemplatePreview() {
139
+ const preview = document.getElementById('templatePreview');
140
+
141
+ let html = `<h3>📊 ${this.template.template.baseName}</h3>`;
142
+ html += `<p style="margin-bottom: 16px; color: #6b7280;">${this.template.template.description}</p>`;
143
+
144
+ this.template.template.tables.forEach(table => {
145
+ html += `
146
+ <div class="table-preview">
147
+ <h4>${table.name}</h4>
148
+ <p>${table.description}</p>
149
+ <div class="field-list">
150
+ ${table.fields.slice(0, 8).map(field =>
151
+ `<span class="field-tag">${field.name}</span>`
152
+ ).join('')}
153
+ ${table.fields.length > 8 ?
154
+ `<span class="field-tag">+${table.fields.length - 8} more</span>` :
155
+ ''
156
+ }
157
+ </div>
158
+ </div>
159
+ `;
160
+ });
161
+
162
+ // Add automation recommendations
163
+ if (this.template.template.automations.length > 0) {
164
+ html += `
165
+ <div style="margin-top: 16px; padding: 12px; background-color: #f0f9ff; border-radius: 6px;">
166
+ <h4 style="margin: 0 0 8px 0; color: #0369a1;">🤖 Recommended Automations</h4>
167
+ <ul style="margin: 0; padding-left: 16px; font-size: 12px;">
168
+ ${this.template.template.automations.map(auto =>
169
+ `<li>${auto.name}: ${auto.description}</li>`
170
+ ).join('')}
171
+ </ul>
172
+ </div>
173
+ `;
174
+ }
175
+
176
+ preview.innerHTML = html;
177
+ }
178
+
179
+ showTemplateLink() {
180
+ const templateSection = document.getElementById('templateLinkSection');
181
+ const manualSection = document.getElementById('manualSetupSection');
182
+
183
+ templateSection.style.display = 'block';
184
+ manualSection.style.display = 'none';
185
+
186
+ // In a real implementation, this would be a working Airtable template URL
187
+ const templateLink = document.getElementById('templateLink');
188
+ templateLink.href = this.template.templateUrl || 'https://airtable.com/universe';
189
+
190
+ // For now, show manual instructions as fallback
191
+ setTimeout(() => {
192
+ templateSection.style.display = 'none';
193
+ manualSection.style.display = 'block';
194
+ this.showManualInstructions();
195
+ }, 100);
196
+ }
197
+
198
+ showManualInstructions() {
199
+ const manualSection = document.getElementById('manualSetupSection');
200
+ const baseNameDisplay = document.getElementById('baseNameDisplay');
201
+ const creationScript = document.getElementById('creationScript');
202
+
203
+ manualSection.style.display = 'block';
204
+ baseNameDisplay.textContent = this.template.template.baseName;
205
+
206
+ // Generate creation script
207
+ const script = this.baseTemplate.generateCreationScript(this.template.template);
208
+ creationScript.value = script;
209
+ }
210
+
211
+ async prepareValidation() {
212
+ // Pre-populate credentials if available
213
+ const existingSettings = await this.getExistingSettings();
214
+
215
+ if (existingSettings.airtableToken) {
216
+ document.getElementById('validationToken').value = existingSettings.airtableToken;
217
+ }
218
+
219
+ if (existingSettings.baseId) {
220
+ document.getElementById('validationBaseId').value = existingSettings.baseId;
221
+ }
222
+
223
+ // If both are available, auto-validate
224
+ if (existingSettings.airtableToken && existingSettings.baseId) {
225
+ setTimeout(() => this.validateBase(), 500);
226
+ }
227
+ }
228
+
229
+ async getExistingSettings() {
230
+ try {
231
+ const response = await chrome.runtime.sendMessage({
232
+ action: 'getSettings'
233
+ });
234
+
235
+ return response.success ? response.settings : {};
236
+ } catch (error) {
237
+ console.error('Failed to get existing settings:', error);
238
+ return {};
239
+ }
240
+ }
241
+
242
+ async validateBase() {
243
+ const token = document.getElementById('validationToken').value.trim();
244
+ const baseId = document.getElementById('validationBaseId').value.trim();
245
+
246
+ if (!token || !baseId) {
247
+ this.showError('Please enter both Personal Access Token and Base ID');
248
+ return;
249
+ }
250
+
251
+ this.showLoading('Validating base structure...');
252
+
253
+ try {
254
+ const client = new AirtableClient(token, baseId);
255
+ const baseTemplate = new BaseTemplate(client);
256
+ const validation = await baseTemplate.validateBaseStructure(baseId);
257
+
258
+ this.displayValidationResults(validation);
259
+
260
+ // Save settings if validation is successful
261
+ if (validation.score >= 40) {
262
+ await this.saveSettings(token, baseId);
263
+ document.getElementById('completeBtn').style.display = 'block';
264
+ }
265
+
266
+ } catch (error) {
267
+ console.error('Validation failed:', error);
268
+ this.showError('Validation failed: ' + error.message);
269
+ } finally {
270
+ this.hideLoading();
271
+ }
272
+ }
273
+
274
+ displayValidationResults(validation) {
275
+ const resultsDiv = document.getElementById('validationResults');
276
+
277
+ let html = `
278
+ <div class="validation-result ${validation.status}">
279
+ <h3>
280
+ ${this.getStatusIcon(validation.status)}
281
+ Base Score: ${validation.score}/${validation.maxScore || 100}
282
+ </h3>
283
+ <p><strong>Status:</strong> ${this.getStatusText(validation.status)}</p>
284
+ `;
285
+
286
+ if (validation.issues && validation.issues.length > 0) {
287
+ html += `
288
+ <div style="margin-top: 12px;">
289
+ <strong>Issues Found:</strong>
290
+ <ul style="margin: 4px 0 0 20px; font-size: 13px;">
291
+ ${validation.issues.map(issue => `<li>${issue}</li>`).join('')}
292
+ </ul>
293
+ </div>
294
+ `;
295
+ }
296
+
297
+ if (validation.recommendations && validation.recommendations.length > 0) {
298
+ html += `
299
+ <div style="margin-top: 12px;">
300
+ <strong>Recommendations:</strong>
301
+ <ul style="margin: 4px 0 0 20px; font-size: 13px;">
302
+ ${validation.recommendations.map(rec => `<li>${rec}</li>`).join('')}
303
+ </ul>
304
+ </div>
305
+ `;
306
+ }
307
+
308
+ html += '</div>';
309
+
310
+ if (validation.status === 'excellent' || validation.status === 'good') {
311
+ html += `
312
+ <div style="margin-top: 16px; padding: 12px; background-color: #d1fae5; border-radius: 6px;">
313
+ <strong>🎉 Great news!</strong> Your base is ready for Airtable Clipper.
314
+ You can start saving LinkedIn profiles and web content right away.
315
+ </div>
316
+ `;
317
+ } else if (validation.status === 'fair') {
318
+ html += `
319
+ <div style="margin-top: 16px; padding: 12px; background-color: #fef3c7; border-radius: 6px;">
320
+ <strong>⚠️ Base needs some work.</strong> The extension will work, but you'll get
321
+ better results by adding the recommended fields and tables.
322
+ </div>
323
+ `;
324
+ } else {
325
+ html += `
326
+ <div style="margin-top: 16px; padding: 12px; background-color: #fee2e2; border-radius: 6px;">
327
+ <strong>❌ Base needs significant improvements.</strong> Please create the missing
328
+ tables and fields for the best experience.
329
+ </div>
330
+ `;
331
+ }
332
+
333
+ resultsDiv.innerHTML = html;
334
+ resultsDiv.style.display = 'block';
335
+ }
336
+
337
+ getStatusIcon(status) {
338
+ const icons = {
339
+ 'excellent': '🌟',
340
+ 'good': '✅',
341
+ 'fair': '⚠️',
342
+ 'needs-work': '❌',
343
+ 'error': '🚫'
344
+ };
345
+ return icons[status] || '❓';
346
+ }
347
+
348
+ getStatusText(status) {
349
+ const texts = {
350
+ 'excellent': 'Perfect! Your base is optimally configured.',
351
+ 'good': 'Great! Your base is well set up for clipping.',
352
+ 'fair': 'Good start, but could use some improvements.',
353
+ 'needs-work': 'Significant setup needed for best results.',
354
+ 'error': 'Unable to validate. Check your credentials.'
355
+ };
356
+ return texts[status] || 'Unknown status';
357
+ }
358
+
359
+ async saveSettings(token, baseId) {
360
+ try {
361
+ await chrome.runtime.sendMessage({
362
+ action: 'saveSettings',
363
+ settings: {
364
+ airtableToken: token,
365
+ baseId: baseId
366
+ }
367
+ });
368
+ } catch (error) {
369
+ console.error('Failed to save settings:', error);
370
+ }
371
+ }
372
+
373
+ async completeSetup() {
374
+ this.showLoading('Completing setup...');
375
+
376
+ try {
377
+ // Close setup wizard and return to main popup
378
+ window.close();
379
+
380
+ // Open main popup to show completed setup
381
+ chrome.action.openPopup();
382
+
383
+ } catch (error) {
384
+ console.error('Failed to complete setup:', error);
385
+ this.showError('Setup completion failed: ' + error.message);
386
+ } finally {
387
+ this.hideLoading();
388
+ }
389
+ }
390
+
391
+ showLoading(message = 'Loading...') {
392
+ const overlay = document.getElementById('loadingOverlay');
393
+ const text = document.getElementById('loadingText');
394
+
395
+ text.textContent = message;
396
+ overlay.style.display = 'flex';
397
+ }
398
+
399
+ hideLoading() {
400
+ const overlay = document.getElementById('loadingOverlay');
401
+ overlay.style.display = 'none';
402
+ }
403
+
404
+ showError(message) {
405
+ // Create a simple error display
406
+ const errorDiv = document.createElement('div');
407
+ errorDiv.style.cssText = `
408
+ position: fixed;
409
+ top: 20px;
410
+ left: 20px;
411
+ right: 20px;
412
+ background-color: #fee2e2;
413
+ color: #991b1b;
414
+ padding: 12px;
415
+ border-radius: 6px;
416
+ border: 1px solid #fca5a5;
417
+ z-index: 10000;
418
+ font-size: 14px;
419
+ `;
420
+ errorDiv.innerHTML = `❌ ${message}`;
421
+
422
+ document.body.appendChild(errorDiv);
423
+
424
+ // Auto-remove after 5 seconds
425
+ setTimeout(() => {
426
+ if (errorDiv.parentNode) {
427
+ errorDiv.parentNode.removeChild(errorDiv);
428
+ }
429
+ }, 5000);
430
+ }
431
+ }
432
+
433
+ // Global functions for HTML onclick handlers
434
+ window.nextStep = function() {
435
+ wizard.nextStep();
436
+ };
437
+
438
+ window.prevStep = function() {
439
+ wizard.prevStep();
440
+ };
441
+
442
+ window.validateBase = function() {
443
+ wizard.validateBase();
444
+ };
445
+
446
+ window.completeSetup = function() {
447
+ wizard.completeSetup();
448
+ };
449
+
450
+ window.copyScript = function() {
451
+ const script = document.getElementById('creationScript');
452
+ script.select();
453
+ document.execCommand('copy');
454
+
455
+ // Show feedback
456
+ const button = event.target;
457
+ const originalText = button.textContent;
458
+ button.textContent = 'Copied!';
459
+ button.style.backgroundColor = '#10b981';
460
+
461
+ setTimeout(() => {
462
+ button.textContent = originalText;
463
+ button.style.backgroundColor = '';
464
+ }, 2000);
465
+ };
466
+
467
+ // Initialize wizard when page loads
468
+ let wizard;
469
+ document.addEventListener('DOMContentLoaded', () => {
470
+ wizard = new BaseSetupWizard();
471
+ });