@rashidazarang/airtable-mcp 2.1.0 → 2.1.1

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 (152) hide show
  1. package/package.json +10 -1
  2. package/.github/ISSUE_TEMPLATE/bug-report.yml +0 -173
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  4. package/.github/ISSUE_TEMPLATE/custom.md +0 -10
  5. package/.github/ISSUE_TEMPLATE/feature-request.yml +0 -209
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  7. package/.github/ISSUE_TEMPLATE/security-report.yml +0 -216
  8. package/.github/pull_request_template.md +0 -245
  9. package/.github/workflows/ci-cd.yml +0 -408
  10. package/.github/workflows/security-audit.yml +0 -316
  11. package/API_DOCUMENTATION.md +0 -897
  12. package/CAPABILITY_REPORT.md +0 -118
  13. package/CLAUDE_INTEGRATION.md +0 -96
  14. package/CODE_OF_CONDUCT.md +0 -181
  15. package/CONTRIBUTING.md +0 -81
  16. package/DEVELOPMENT.md +0 -190
  17. package/Dockerfile +0 -39
  18. package/Dockerfile.node +0 -20
  19. package/Dockerfile.production +0 -127
  20. package/IMPROVEMENT_PROPOSAL.md +0 -371
  21. package/INSTALLATION.md +0 -183
  22. package/ISSUE_RESPONSES.md +0 -171
  23. package/MCP_REVIEW_SUMMARY.md +0 -142
  24. package/QUICK_START.md +0 -60
  25. package/RELEASE_NOTES_v1.2.0.md +0 -50
  26. package/RELEASE_NOTES_v1.2.1.md +0 -40
  27. package/RELEASE_NOTES_v1.2.2.md +0 -48
  28. package/RELEASE_NOTES_v1.2.3.md +0 -105
  29. package/RELEASE_NOTES_v1.2.4.md +0 -60
  30. package/RELEASE_NOTES_v1.4.0.md +0 -104
  31. package/RELEASE_NOTES_v1.5.0.md +0 -185
  32. package/RELEASE_NOTES_v1.6.0.md +0 -248
  33. package/SECURITY_NOTICE.md +0 -40
  34. package/airtable-clipper/CHANGELOG.md +0 -198
  35. package/airtable-clipper/CHROME_STORE_SUBMISSION.md +0 -343
  36. package/airtable-clipper/LAUNCH_STRATEGY.md +0 -495
  37. package/airtable-clipper/LICENSE +0 -21
  38. package/airtable-clipper/OAUTH_SETUP.md +0 -51
  39. package/airtable-clipper/PRIVACY_POLICY.md +0 -187
  40. package/airtable-clipper/README.md +0 -575
  41. package/airtable-clipper/SUBMIT_TO_CHROME_STORE.md +0 -273
  42. package/airtable-clipper/build.sh +0 -85
  43. package/airtable-clipper/docs/QUICK_START.md +0 -99
  44. package/airtable-clipper/docs/SETUP.md +0 -291
  45. package/airtable-clipper/extension/background.js +0 -337
  46. package/airtable-clipper/extension/base-setup.html +0 -324
  47. package/airtable-clipper/extension/base-setup.js +0 -471
  48. package/airtable-clipper/extension/content.js +0 -771
  49. package/airtable-clipper/extension/icons/README.md +0 -69
  50. package/airtable-clipper/extension/icons/icon-16.png +0 -3
  51. package/airtable-clipper/extension/manifest.json +0 -73
  52. package/airtable-clipper/extension/popup.html +0 -144
  53. package/airtable-clipper/extension/popup.js +0 -475
  54. package/airtable-clipper/extension/styles/content.css +0 -229
  55. package/airtable-clipper/extension/styles/popup.css +0 -477
  56. package/airtable-clipper/privacy-policy.md +0 -63
  57. package/airtable-clipper/releases/v1.0.0/background.js +0 -337
  58. package/airtable-clipper/releases/v1.0.0/base-setup.html +0 -324
  59. package/airtable-clipper/releases/v1.0.0/base-setup.js +0 -471
  60. package/airtable-clipper/releases/v1.0.0/content.js +0 -771
  61. package/airtable-clipper/releases/v1.0.0/icons/README.md +0 -69
  62. package/airtable-clipper/releases/v1.0.0/icons/icon-128.png +0 -2
  63. package/airtable-clipper/releases/v1.0.0/icons/icon-16.png +0 -3
  64. package/airtable-clipper/releases/v1.0.0/icons/icon-32.png +0 -2
  65. package/airtable-clipper/releases/v1.0.0/icons/icon-48.png +0 -2
  66. package/airtable-clipper/releases/v1.0.0/manifest.json +0 -73
  67. package/airtable-clipper/releases/v1.0.0/popup.html +0 -144
  68. package/airtable-clipper/releases/v1.0.0/popup.js +0 -475
  69. package/airtable-clipper/releases/v1.0.0/sidepanel.html +0 -25
  70. package/airtable-clipper/releases/v1.0.0/styles/content.css +0 -229
  71. package/airtable-clipper/releases/v1.0.0/styles/popup.css +0 -477
  72. package/airtable-clipper/releases/v1.0.1/background.js +0 -337
  73. package/airtable-clipper/releases/v1.0.1/base-setup.html +0 -324
  74. package/airtable-clipper/releases/v1.0.1/base-setup.js +0 -471
  75. package/airtable-clipper/releases/v1.0.1/content.js +0 -771
  76. package/airtable-clipper/releases/v1.0.1/icons/README.md +0 -69
  77. package/airtable-clipper/releases/v1.0.1/icons/icon-128.png +0 -2
  78. package/airtable-clipper/releases/v1.0.1/icons/icon-16.png +0 -3
  79. package/airtable-clipper/releases/v1.0.1/icons/icon-32.png +0 -2
  80. package/airtable-clipper/releases/v1.0.1/icons/icon-48.png +0 -2
  81. package/airtable-clipper/releases/v1.0.1/manifest.json +0 -70
  82. package/airtable-clipper/releases/v1.0.1/popup.html +0 -157
  83. package/airtable-clipper/releases/v1.0.1/popup.js +0 -562
  84. package/airtable-clipper/releases/v1.0.1/sidepanel.html +0 -25
  85. package/airtable-clipper/releases/v1.0.1/styles/content.css +0 -229
  86. package/airtable-clipper/releases/v1.0.1/styles/popup.css +0 -647
  87. package/airtable-clipper/releases/v1.0.2/background.js +0 -337
  88. package/airtable-clipper/releases/v1.0.2/base-setup.html +0 -324
  89. package/airtable-clipper/releases/v1.0.2/base-setup.js +0 -471
  90. package/airtable-clipper/releases/v1.0.2/content.js +0 -771
  91. package/airtable-clipper/releases/v1.0.2/icons/README.md +0 -69
  92. package/airtable-clipper/releases/v1.0.2/icons/icon-128.png +0 -2
  93. package/airtable-clipper/releases/v1.0.2/icons/icon-16.png +0 -3
  94. package/airtable-clipper/releases/v1.0.2/icons/icon-32.png +0 -2
  95. package/airtable-clipper/releases/v1.0.2/icons/icon-48.png +0 -2
  96. package/airtable-clipper/releases/v1.0.2/manifest.json +0 -62
  97. package/airtable-clipper/releases/v1.0.2/popup.html +0 -157
  98. package/airtable-clipper/releases/v1.0.2/popup.js +0 -567
  99. package/airtable-clipper/releases/v1.0.2/sidepanel.html +0 -25
  100. package/airtable-clipper/releases/v1.0.2/styles/content.css +0 -229
  101. package/airtable-clipper/releases/v1.0.2/styles/popup.css +0 -647
  102. package/airtable-clipper/terms-of-service.md +0 -124
  103. package/airtable-clipper/test-credentials.md +0 -61
  104. package/airtable-clipper/test-extension/background.js +0 -337
  105. package/airtable-clipper/test-extension/base-setup.html +0 -324
  106. package/airtable-clipper/test-extension/base-setup.js +0 -471
  107. package/airtable-clipper/test-extension/content.js +0 -873
  108. package/airtable-clipper/test-extension/icons/README.md +0 -69
  109. package/airtable-clipper/test-extension/icons/icon-128.png +0 -2
  110. package/airtable-clipper/test-extension/icons/icon-16.png +0 -3
  111. package/airtable-clipper/test-extension/icons/icon-32.png +0 -2
  112. package/airtable-clipper/test-extension/icons/icon-48.png +0 -2
  113. package/airtable-clipper/test-extension/manifest.json +0 -72
  114. package/airtable-clipper/test-extension/popup.html +0 -274
  115. package/airtable-clipper/test-extension/popup.js +0 -729
  116. package/airtable-clipper/test-extension/sidepanel.html +0 -25
  117. package/airtable-clipper/test-extension/styles/content.css +0 -229
  118. package/airtable-clipper/test-extension/styles/popup.css +0 -794
  119. package/airtable_mcp/__init__.py +0 -5
  120. package/airtable_mcp/src/server.py +0 -329
  121. package/airtable_mcp_v2.js +0 -1505
  122. package/airtable_mcp_v2_oauth.js +0 -1048
  123. package/airtable_mcp_v3_advanced.js +0 -1161
  124. package/cleanup.sh +0 -71
  125. package/docker-compose.production.yml +0 -366
  126. package/helm/airtable-mcp/Chart.yaml +0 -122
  127. package/helm/airtable-mcp/values.yaml +0 -538
  128. package/index.js +0 -179
  129. package/inspector.py +0 -148
  130. package/inspector_server.py +0 -337
  131. package/k8s/deployment.yaml +0 -402
  132. package/k8s/namespace.yaml +0 -108
  133. package/k8s/service.yaml +0 -194
  134. package/monitoring/alerts.yml +0 -289
  135. package/monitoring/prometheus.yml +0 -224
  136. package/publish-steps.txt +0 -27
  137. package/quick_test.sh +0 -30
  138. package/requirements.txt +0 -10
  139. package/setup.py +0 -29
  140. package/simple_airtable_server.py +0 -151
  141. package/smithery.yaml +0 -45
  142. package/test_all_features.sh +0 -146
  143. package/test_all_operations.sh +0 -120
  144. package/test_client.py +0 -70
  145. package/test_enhanced_features.js +0 -389
  146. package/test_mcp_comprehensive.js +0 -163
  147. package/test_mock_server.js +0 -180
  148. package/test_v1.4.0_final.sh +0 -131
  149. package/test_v1.5.0_comprehensive.sh +0 -96
  150. package/test_v1.5.0_final.sh +0 -224
  151. package/test_v1.6.0_comprehensive.sh +0 -187
  152. package/test_webhooks.sh +0 -105
@@ -1,471 +0,0 @@
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
- });