chrometools-mcp 1.8.2 → 2.2.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.
@@ -27,6 +27,56 @@ export function generateRecorderScript() {
27
27
  (function() {
28
28
  'use strict';
29
29
 
30
+ // ==========================
31
+ // URL TO PROJECT UTILITIES
32
+ // ==========================
33
+
34
+ /**
35
+ * Extract project ID from URL (browser version)
36
+ * @param {string} url - Full URL
37
+ * @returns {string} - Project ID
38
+ */
39
+ function urlToProjectId(url) {
40
+ try {
41
+ if (url.startsWith('file://')) {
42
+ return 'local';
43
+ }
44
+
45
+ const urlObj = new URL(url);
46
+ let hostname = urlObj.hostname.toLowerCase();
47
+ const port = urlObj.port;
48
+
49
+ hostname = hostname.replace(/^www\\./, '');
50
+ const parts = hostname.split('.');
51
+
52
+ if (parts.length === 1) {
53
+ const projectId = sanitizeProjectId(parts[0]);
54
+ return port ? \`\${projectId}-\${port}\` : projectId;
55
+ }
56
+
57
+ const mainDomain = parts[parts.length - 2];
58
+ const projectId = sanitizeProjectId(mainDomain);
59
+ return port ? \`\${projectId}-\${port}\` : projectId;
60
+
61
+ } catch (error) {
62
+ console.error('[url-to-project] Invalid URL:', url, error);
63
+ return 'unknown';
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Sanitize project ID (browser version)
69
+ * @param {string} id - Raw project ID
70
+ * @returns {string} - Sanitized ID
71
+ */
72
+ function sanitizeProjectId(id) {
73
+ return id
74
+ .toLowerCase()
75
+ .replace(/[^a-z0-9-]/g, '-')
76
+ .replace(/-+/g, '-')
77
+ .replace(/^-|-$/g, '');
78
+ }
79
+
30
80
  // ==========================
31
81
  // RECORDER STATE
32
82
  // ==========================
@@ -91,9 +141,28 @@ export function generateRecorderScript() {
91
141
  hoverDeletionCandidates: Array.from(state.hoverDeletionCandidates),
92
142
  timestamp: Date.now()
93
143
  };
94
- localStorage.setItem(STORAGE_KEY, JSON.stringify(persistentState));
144
+
145
+ // Validate state before stringifying
146
+ let jsonString;
147
+ try {
148
+ jsonString = JSON.stringify(persistentState);
149
+ } catch (stringifyError) {
150
+ console.error('[Recorder] Failed to stringify state:', stringifyError.message);
151
+ console.error('[Recorder] Problematic state:', persistentState);
152
+ return;
153
+ }
154
+
155
+ // Validate JSON string
156
+ if (!jsonString || typeof jsonString !== 'string' || jsonString.length === 0) {
157
+ console.error('[Recorder] Invalid JSON string generated');
158
+ return;
159
+ }
160
+
161
+ // Save to localStorage
162
+ localStorage.setItem(STORAGE_KEY, jsonString);
95
163
  } catch (error) {
96
164
  console.error('[Recorder] Failed to save state:', error);
165
+ // Don't let save errors crash the recorder
97
166
  }
98
167
  }
99
168
 
@@ -108,11 +177,37 @@ export function generateRecorderScript() {
108
177
  const saved = localStorage.getItem(STORAGE_KEY);
109
178
  if (!saved) return null;
110
179
 
111
- const persistentState = JSON.parse(saved);
180
+ // Validate JSON before parsing
181
+ if (typeof saved !== 'string' || saved.trim().length === 0) {
182
+ console.warn('[Recorder] Invalid state data in localStorage, clearing...');
183
+ clearStateFromLocalStorage();
184
+ resetClearingFlag();
185
+ return null;
186
+ }
187
+
188
+ let persistentState;
189
+ try {
190
+ persistentState = JSON.parse(saved);
191
+ } catch (parseError) {
192
+ console.error('[Recorder] Corrupted state data in localStorage:', parseError.message);
193
+ console.error('[Recorder] Data preview:', saved.substring(0, 100));
194
+ clearStateFromLocalStorage();
195
+ resetClearingFlag();
196
+ return null;
197
+ }
198
+
199
+ // Validate parsed state structure
200
+ if (!persistentState || typeof persistentState !== 'object') {
201
+ console.warn('[Recorder] Invalid state structure, clearing...');
202
+ clearStateFromLocalStorage();
203
+ resetClearingFlag();
204
+ return null;
205
+ }
112
206
 
113
207
  // Check if state is not too old (max 24 hours)
114
208
  const maxAge = 24 * 60 * 60 * 1000; // 24 hours
115
- if (Date.now() - persistentState.timestamp > maxAge) {
209
+ if (Date.now() - (persistentState.timestamp || 0) > maxAge) {
210
+ console.log('[Recorder] State too old, clearing...');
116
211
  clearStateFromLocalStorage();
117
212
  resetClearingFlag();
118
213
  return null;
@@ -121,6 +216,13 @@ export function generateRecorderScript() {
121
216
  return persistentState;
122
217
  } catch (error) {
123
218
  console.error('[Recorder] Failed to load state:', error);
219
+ // Clear potentially corrupted state
220
+ try {
221
+ clearStateFromLocalStorage();
222
+ resetClearingFlag();
223
+ } catch (clearError) {
224
+ console.error('[Recorder] Failed to clear corrupted state:', clearError);
225
+ }
124
226
  return null;
125
227
  }
126
228
  }
@@ -1341,7 +1443,10 @@ export function generateRecorderScript() {
1341
1443
  // Call MCP server via exposed function
1342
1444
  if (window.saveScenarioToMCP) {
1343
1445
  try {
1344
- const result = await window.saveScenarioToMCP(scenario);
1446
+ // Extract projectId from URL
1447
+ const urlProjectId = urlToProjectId(state.startUrl || window.location.href);
1448
+
1449
+ const result = await window.saveScenarioToMCP(scenario, urlProjectId);
1345
1450
  if (result.success) {
1346
1451
  // Set clearing flag to prevent any saves during cleanup
1347
1452
  isClearing = true;
@@ -1599,9 +1704,10 @@ export function generateRecorderScript() {
1599
1704
 
1600
1705
  /**
1601
1706
  * Inject recorder into page
1707
+ * Project ID will be automatically determined from URL in browser context
1602
1708
  * @param {Object} page - Puppeteer page instance
1603
1709
  */
1604
- export async function injectRecorder(page, baseDir) {
1710
+ export async function injectRecorder(page) {
1605
1711
  try {
1606
1712
  // Check if recorder is already injected
1607
1713
  const alreadyInjected = await page.evaluate(() => {
@@ -1629,20 +1735,20 @@ export async function injectRecorder(page, baseDir) {
1629
1735
 
1630
1736
  // Only expose functions if they don't exist yet
1631
1737
  if (!functionExists) {
1632
- await page.exposeFunction('saveScenarioToMCP', async (scenarioData) => {
1738
+ // saveScenarioToMCP now receives urlProjectId from browser
1739
+ await page.exposeFunction('saveScenarioToMCP', async (scenarioData, urlProjectId) => {
1633
1740
  const { saveScenario } = await import('./scenario-storage.js');
1634
- return await saveScenario(scenarioData, baseDir);
1741
+ return await saveScenario(scenarioData, urlProjectId);
1635
1742
  });
1636
1743
 
1744
+ // listScenariosFromMCP returns ALL scenarios (no filtering by project)
1637
1745
  await page.exposeFunction('listScenariosFromMCP', async () => {
1638
- const { loadIndex } = await import('./scenario-storage.js');
1639
- const path = await import('path');
1746
+ const { listScenarios } = await import('./scenario-storage.js');
1640
1747
  try {
1641
- const scenariosDir = path.join(baseDir, 'scenarios');
1642
- const index = await loadIndex(scenariosDir);
1748
+ const scenarios = await listScenarios(null, true); // null projectId, allProjects=true
1643
1749
  return {
1644
1750
  success: true,
1645
- scenarios: Object.values(index)
1751
+ scenarios: scenarios
1646
1752
  };
1647
1753
  } catch (error) {
1648
1754
  return {