chrometools-mcp 1.9.1 → 2.3.2

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
  // ==========================
@@ -1393,7 +1443,10 @@ export function generateRecorderScript() {
1393
1443
  // Call MCP server via exposed function
1394
1444
  if (window.saveScenarioToMCP) {
1395
1445
  try {
1396
- 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);
1397
1450
  if (result.success) {
1398
1451
  // Set clearing flag to prevent any saves during cleanup
1399
1452
  isClearing = true;
@@ -1651,9 +1704,10 @@ export function generateRecorderScript() {
1651
1704
 
1652
1705
  /**
1653
1706
  * Inject recorder into page
1707
+ * Project ID will be automatically determined from URL in browser context
1654
1708
  * @param {Object} page - Puppeteer page instance
1655
1709
  */
1656
- export async function injectRecorder(page, baseDir) {
1710
+ export async function injectRecorder(page) {
1657
1711
  try {
1658
1712
  // Check if recorder is already injected
1659
1713
  const alreadyInjected = await page.evaluate(() => {
@@ -1681,20 +1735,20 @@ export async function injectRecorder(page, baseDir) {
1681
1735
 
1682
1736
  // Only expose functions if they don't exist yet
1683
1737
  if (!functionExists) {
1684
- await page.exposeFunction('saveScenarioToMCP', async (scenarioData) => {
1738
+ // saveScenarioToMCP now receives urlProjectId from browser
1739
+ await page.exposeFunction('saveScenarioToMCP', async (scenarioData, urlProjectId) => {
1685
1740
  const { saveScenario } = await import('./scenario-storage.js');
1686
- return await saveScenario(scenarioData, baseDir);
1741
+ return await saveScenario(scenarioData, urlProjectId);
1687
1742
  });
1688
1743
 
1744
+ // listScenariosFromMCP returns ALL scenarios (no filtering by project)
1689
1745
  await page.exposeFunction('listScenariosFromMCP', async () => {
1690
- const { loadIndex } = await import('./scenario-storage.js');
1691
- const path = await import('path');
1746
+ const { listScenarios } = await import('./scenario-storage.js');
1692
1747
  try {
1693
- const scenariosDir = path.join(baseDir, 'scenarios');
1694
- const index = await loadIndex(scenariosDir);
1748
+ const scenarios = await listScenarios(null, true); // null projectId, allProjects=true
1695
1749
  return {
1696
1750
  success: true,
1697
- scenarios: Object.values(index)
1751
+ scenarios: scenarios
1698
1752
  };
1699
1753
  } catch (error) {
1700
1754
  return {
@@ -21,16 +21,16 @@ const debugLog = DEBUG_MODE ? console.error : () => {};
21
21
  * @param {string} scenarioName - Scenario to execute
22
22
  * @param {Object} page - Puppeteer page instance
23
23
  * @param {Object} params - Parameters for scenario
24
- * @param {Object} options - Execution options { executeDependencies, skipConditions, maxRetries, timeout, baseDir }
24
+ * @param {Object} options - Execution options { executeDependencies, skipConditions, maxRetries, timeout }
25
25
  * @returns {Object} - Execution result
26
26
  */
27
27
  export async function executeScenario(scenarioName, page, params = {}, options = {}) {
28
28
  const {
29
- executeDependencies = true, // NEW: Execute dependencies by default
29
+ executeDependencies = true, // Execute dependencies by default
30
30
  skipConditions = false,
31
31
  maxRetries = 3,
32
32
  timeout = 30000,
33
- baseDir // Base directory for scenarios
33
+ projectId = null // Optional projectId for disambiguation
34
34
  } = options;
35
35
 
36
36
  const result = {
@@ -45,30 +45,50 @@ export async function executeScenario(scenarioName, page, params = {}, options =
45
45
  const startTime = Date.now();
46
46
 
47
47
  try {
48
- // Load scenario index
49
- const path = await import('path');
50
- const scenariosDir = path.join(baseDir, 'scenarios');
51
- const scenarioIndex = await loadIndex(scenariosDir);
48
+ // Load scenario to get its metadata (needed for dependency resolution)
49
+ const initialScenario = await loadScenario(scenarioName, false, projectId);
52
50
 
53
- let chain = [scenarioName]; // Default: execute only the requested scenario
51
+ if (!initialScenario) {
52
+ result.errors.push(`Scenario "${scenarioName}" not found`);
53
+ return result;
54
+ }
54
55
 
55
- // Resolve and execute dependencies if enabled
56
- if (executeDependencies) {
57
- const resolution = resolveDependencies(scenarioName, scenarioIndex, { skipConditions });
56
+ // Check for name collision
57
+ if (initialScenario.collision) {
58
+ result.errors.push(initialScenario.message);
59
+ result.availableProjectIds = initialScenario.availableProjectIds;
60
+ return result;
61
+ }
58
62
 
59
- if (resolution.errors.length > 0) {
60
- result.errors.push(...resolution.errors);
61
- return result;
62
- }
63
+ let chain = [{ name: scenarioName, projectId }]; // Default: execute only the requested scenario
63
64
 
64
- chain = resolution.chain; // Use full dependency chain
65
+ // Resolve and execute dependencies if enabled
66
+ if (executeDependencies && initialScenario.metadata?.dependencies) {
67
+ // Build a simplified index from metadata for dependency resolution
68
+ // In the new system, we need to resolve cross-project dependencies
69
+ // Dependencies inherit parent's projectId unless they specify their own
70
+ chain = [
71
+ ...initialScenario.metadata.dependencies.map(dep => ({
72
+ name: dep.scenario,
73
+ projectId: dep.projectId || projectId // Use explicit projectId or inherit from parent
74
+ })),
75
+ { name: scenarioName, projectId }
76
+ ];
65
77
  }
66
78
 
67
79
  // Execute chain in order
68
- for (const name of chain) {
69
- const scenario = await loadScenario(name, false, baseDir);
80
+ for (const item of chain) {
81
+ const scenario = await loadScenario(item.name, true, item.projectId); // Load with secrets, using item's projectId
82
+
70
83
  if (!scenario) {
71
- result.errors.push(`Scenario "${name}" not found`);
84
+ result.errors.push(`Scenario "${item.name}" not found`);
85
+ return result;
86
+ }
87
+
88
+ // Check for name collision in dependencies
89
+ if (scenario.collision) {
90
+ result.errors.push(`Dependency "${item.name}": ${scenario.message}`);
91
+ result.availableProjectIds = scenario.availableProjectIds;
72
92
  return result;
73
93
  }
74
94
 
@@ -80,19 +100,15 @@ export async function executeScenario(scenarioName, page, params = {}, options =
80
100
  const shouldExecute = await checkDependencyCondition(dep.condition, context);
81
101
 
82
102
  if (!shouldExecute) {
83
- debugLog(`Skipping scenario "${name}" due to condition`);
103
+ debugLog(`Skipping scenario "${item.name}" due to condition`);
84
104
  continue;
85
105
  }
86
106
  }
87
107
  }
88
108
  }
89
109
 
90
- // Load secrets
91
- const secretsDir = path.join(baseDir, 'secrets');
92
- const secrets = await loadSecrets(name, secretsDir);
93
-
94
110
  // Merge secrets with params
95
- const executionParams = { ...params, ...secrets };
111
+ const executionParams = { ...params, ...(scenario.secrets || {}) };
96
112
 
97
113
  // Execute scenario
98
114
  const scenarioResult = await executeSingleScenario(scenario, page, executionParams, {
@@ -100,7 +116,7 @@ export async function executeScenario(scenarioName, page, params = {}, options =
100
116
  timeout
101
117
  });
102
118
 
103
- result.executedScenarios.push(name);
119
+ result.executedScenarios.push(item.name);
104
120
 
105
121
  if (!scenarioResult.success) {
106
122
  result.errors.push(...scenarioResult.errors);
@@ -170,7 +186,11 @@ async function executeSingleScenario(scenario, page, params = {}, options = {})
170
186
 
171
187
  // Validate final URL if exitUrl is specified in metadata
172
188
  if (scenario.metadata?.exitUrl) {
173
- const currentUrl = page.url();
189
+ // Wait a bit for any pending navigation/redirects to complete
190
+ await new Promise(resolve => setTimeout(resolve, 500));
191
+
192
+ // Get current URL from the page (more reliable than page.url() for recent navigation)
193
+ const currentUrl = await page.evaluate(() => window.location.href);
174
194
  const expectedUrl = scenario.metadata.exitUrl;
175
195
 
176
196
  // Normalize URLs for comparison (remove trailing slashes, fragments)
@@ -4,21 +4,168 @@
4
4
  * Manages scenario and secrets storage:
5
5
  * 1. Save/load scenarios to/from files
6
6
  * 2. Save/load secrets separately
7
- * 3. Maintain scenario index with metadata
7
+ * 3. Maintain project-specific and global indexes
8
8
  * 4. Ensure .gitignore for secrets directory
9
9
  */
10
10
 
11
11
  import fs from 'fs/promises';
12
+ import fssync from 'fs';
12
13
  import path from 'path';
14
+ import { homedir } from 'os';
13
15
 
14
16
  // Constants
15
17
  const INDEX_FILE = 'index.json';
16
18
  const GITIGNORE_FILE = '.gitignore';
19
+ const BASE_STORAGE_DIR = path.join(homedir(), '.config', 'chrometools-mcp');
20
+ const GLOBAL_INDEX_PATH = path.join(BASE_STORAGE_DIR, 'index.json');
21
+ const PROJECTS_DIR = path.join(BASE_STORAGE_DIR, 'projects');
22
+
23
+ /**
24
+ * Load global index from ~/.config/chrometools-mcp/index.json
25
+ * @returns {object} - Global index object
26
+ */
27
+ function loadGlobalIndex() {
28
+ try {
29
+ const data = fssync.readFileSync(GLOBAL_INDEX_PATH, 'utf-8');
30
+ return JSON.parse(data);
31
+ } catch (err) {
32
+ // Index doesn't exist yet, return empty structure
33
+ return {
34
+ version: '2.0',
35
+ projects: {}
36
+ };
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Save global index to ~/.config/chrometools-mcp/index.json
42
+ * @param {object} index - Global index object
43
+ */
44
+ async function saveGlobalIndex(index) {
45
+ await fs.mkdir(BASE_STORAGE_DIR, { recursive: true });
46
+ await fs.writeFile(GLOBAL_INDEX_PATH, JSON.stringify(index, null, 2), 'utf-8');
47
+ }
48
+
49
+ /**
50
+ * Update global index with scenario metadata
51
+ * @param {string} projectId - Project identifier
52
+ * @param {string} projectPath - Full path to project root
53
+ * @param {string} scenarioName - Scenario name
54
+ * @param {object} metadata - Scenario metadata
55
+ */
56
+ async function updateGlobalIndex(projectId, projectPath, scenarioName, metadata) {
57
+ const index = loadGlobalIndex();
58
+
59
+ if (!index.projects[projectId]) {
60
+ index.projects[projectId] = {
61
+ projectPath,
62
+ lastAccessed: new Date().toISOString(),
63
+ scenarios: {}
64
+ };
65
+ }
66
+
67
+ index.projects[projectId].scenarios[scenarioName] = {
68
+ name: scenarioName,
69
+ description: metadata.description || '',
70
+ tags: metadata.tags || [],
71
+ dependencies: metadata.dependencies || [],
72
+ parameters: metadata.parameters || {},
73
+ outputs: metadata.outputs || [],
74
+ entryUrl: metadata.entryUrl || null,
75
+ exitUrl: metadata.exitUrl || null,
76
+ createdAt: index.projects[projectId].scenarios[scenarioName]?.createdAt || new Date().toISOString(),
77
+ updatedAt: new Date().toISOString()
78
+ };
79
+
80
+ index.projects[projectId].lastAccessed = new Date().toISOString();
81
+
82
+ await saveGlobalIndex(index);
83
+ }
84
+
85
+ /**
86
+ * Remove scenario from global index
87
+ * @param {string} projectId - Project identifier
88
+ * @param {string} scenarioName - Scenario name
89
+ */
90
+ async function removeFromGlobalIndex(projectId, scenarioName) {
91
+ const index = loadGlobalIndex();
92
+
93
+ if (index.projects[projectId]?.scenarios[scenarioName]) {
94
+ delete index.projects[projectId].scenarios[scenarioName];
95
+
96
+ // Remove project if no scenarios left
97
+ if (Object.keys(index.projects[projectId].scenarios).length === 0) {
98
+ delete index.projects[projectId];
99
+ }
100
+
101
+ await saveGlobalIndex(index);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Find scenario in global index
107
+ * @param {string} scenarioName - Scenario name
108
+ * @param {string|null} projectId - Optional project filter
109
+ * @returns {object|null} - { projectId, metadata } or null
110
+ */
111
+ /**
112
+ * Find all scenarios with the given name across all projects
113
+ * @param {string} scenarioName - Scenario name
114
+ * @returns {Array} - Array of { projectId, metadata } objects
115
+ */
116
+ function findAllScenariosWithName(scenarioName) {
117
+ const index = loadGlobalIndex();
118
+ const results = [];
119
+
120
+ for (const [pid, project] of Object.entries(index.projects)) {
121
+ if (project.scenarios[scenarioName]) {
122
+ results.push({
123
+ projectId: pid,
124
+ metadata: project.scenarios[scenarioName]
125
+ });
126
+ }
127
+ }
128
+
129
+ return results;
130
+ }
131
+
132
+ function findScenarioInGlobalIndex(scenarioName, projectId = null) {
133
+ const index = loadGlobalIndex();
134
+
135
+ if (projectId) {
136
+ // Search in specific project
137
+ if (index.projects[projectId]?.scenarios[scenarioName]) {
138
+ return {
139
+ projectId,
140
+ metadata: index.projects[projectId].scenarios[scenarioName]
141
+ };
142
+ }
143
+ return null;
144
+ }
145
+
146
+ // Search in all projects - check for collisions
147
+ const allMatches = findAllScenariosWithName(scenarioName);
148
+
149
+ if (allMatches.length === 0) {
150
+ return null;
151
+ }
152
+
153
+ if (allMatches.length > 1) {
154
+ // Multiple scenarios with same name - return collision error
155
+ return {
156
+ collision: true,
157
+ matches: allMatches.map(m => m.projectId)
158
+ };
159
+ }
160
+
161
+ // Single match - return it
162
+ return allMatches[0];
163
+ }
17
164
 
18
165
  /**
19
166
  * Initialize storage directories
20
167
  * Creates directories and ensures .gitignore exists
21
- * @param {string} baseDir - Base directory for scenarios and secrets
168
+ * @param {string} baseDir - Base directory for scenarios and secrets (project directory)
22
169
  */
23
170
  export async function initializeStorage(baseDir) {
24
171
  const scenariosDir = path.join(baseDir, 'scenarios');
@@ -64,12 +211,18 @@ async function ensureSecretsGitignore(secretsDir) {
64
211
  /**
65
212
  * Save scenario to file
66
213
  * @param {Object} scenario - Scenario data
67
- * @param {string} baseDir - Base directory for storage
214
+ * @param {string} urlProjectId - Project identifier extracted from URL (e.g., "google", "localhost-3000")
68
215
  * @returns {Object} - { success: boolean, path: string, error?: string }
69
216
  */
70
- export async function saveScenario(scenario, baseDir) {
217
+ export async function saveScenario(scenario, urlProjectId) {
71
218
  try {
72
- await initializeStorage(baseDir);
219
+ // Use URL-based projectId directly
220
+ const projectId = urlProjectId;
221
+ const projectPath = `url://${urlProjectId}`;
222
+
223
+ // Get project directory
224
+ const projectDir = path.join(PROJECTS_DIR, projectId);
225
+ await initializeStorage(projectDir);
73
226
 
74
227
  const { name, metadata, chain, secrets } = scenario;
75
228
 
@@ -81,13 +234,20 @@ export async function saveScenario(scenario, baseDir) {
81
234
  };
82
235
  }
83
236
 
84
- const scenariosDir = path.join(baseDir, 'scenarios');
85
- const secretsDir = path.join(baseDir, 'secrets');
237
+ const scenariosDir = path.join(projectDir, 'scenarios');
238
+ const secretsDir = path.join(projectDir, 'secrets');
239
+
240
+ // Add project info to metadata
241
+ const enhancedMetadata = {
242
+ ...(metadata || {}),
243
+ projectId,
244
+ projectPath
245
+ };
86
246
 
87
247
  // Save main scenario file (without secrets)
88
248
  const scenarioData = {
89
249
  name,
90
- metadata: metadata || {},
250
+ metadata: enhancedMetadata,
91
251
  chain,
92
252
  version: '1.0',
93
253
  createdAt: new Date().toISOString()
@@ -101,8 +261,11 @@ export async function saveScenario(scenario, baseDir) {
101
261
  await saveSecrets(name, secrets, secretsDir);
102
262
  }
103
263
 
104
- // Update index
105
- await updateIndex(name, metadata, scenariosDir);
264
+ // Update project-local index
265
+ await updateIndex(name, enhancedMetadata, scenariosDir);
266
+
267
+ // Update global index
268
+ await updateGlobalIndex(projectId, projectPath, name, enhancedMetadata);
106
269
 
107
270
  return {
108
271
  success: true,
@@ -120,19 +283,40 @@ export async function saveScenario(scenario, baseDir) {
120
283
  * Load scenario from file
121
284
  * @param {string} name - Scenario name
122
285
  * @param {boolean} includeSecrets - Whether to load secrets
123
- * @param {string} baseDir - Base directory for storage
286
+ * @param {string|null} projectId - Optional project ID filter
124
287
  * @returns {Object} - Scenario data or null
125
288
  */
126
- export async function loadScenario(name, includeSecrets = false, baseDir) {
289
+ export async function loadScenario(name, includeSecrets = false, projectId = null) {
127
290
  try {
128
- const scenariosDir = path.join(baseDir, 'scenarios');
291
+ // Find scenario in global index
292
+ const result = findScenarioInGlobalIndex(name, projectId);
293
+
294
+ if (!result) {
295
+ console.error(`Scenario "${name}" not found${projectId ? ` in project "${projectId}"` : ''}`);
296
+ return null;
297
+ }
298
+
299
+ // Check for name collision
300
+ if (result.collision) {
301
+ const error = {
302
+ collision: true,
303
+ message: `Multiple scenarios named "${name}" found. Please specify projectId.`,
304
+ availableProjectIds: result.matches
305
+ };
306
+ console.error(error.message, 'Available projectIds:', result.matches);
307
+ return error;
308
+ }
309
+
310
+ // Load from project directory
311
+ const projectDir = path.join(PROJECTS_DIR, result.projectId);
312
+ const scenariosDir = path.join(projectDir, 'scenarios');
129
313
  const scenarioPath = path.join(scenariosDir, `${name}.json`);
130
314
  const content = await fs.readFile(scenarioPath, 'utf-8');
131
315
  const scenario = JSON.parse(content);
132
316
 
133
317
  // Load secrets if requested
134
318
  if (includeSecrets) {
135
- const secretsDir = path.join(baseDir, 'secrets');
319
+ const secretsDir = path.join(projectDir, 'secrets');
136
320
  const secrets = await loadSecrets(name, secretsDir);
137
321
  if (secrets) {
138
322
  scenario.secrets = secrets;
@@ -235,25 +419,51 @@ async function saveIndex(index, scenariosDir) {
235
419
 
236
420
  /**
237
421
  * List all available scenarios
238
- * @param {string} baseDir - Base directory for storage
422
+ * @param {string} currentProjectId - Current project identifier
423
+ * @param {boolean} allProjects - Whether to list scenarios from all projects
239
424
  * @returns {Array} - Array of scenario metadata
240
425
  */
241
- export async function listScenarios(baseDir) {
242
- const scenariosDir = path.join(baseDir, 'scenarios');
243
- const index = await loadIndex(scenariosDir);
244
- return Object.values(index);
426
+ export async function listScenarios(currentProjectId, allProjects = false) {
427
+ const globalIndex = loadGlobalIndex();
428
+ const results = [];
429
+
430
+ if (allProjects) {
431
+ // Return scenarios from all projects
432
+ for (const [projectId, project] of Object.entries(globalIndex.projects)) {
433
+ for (const scenario of Object.values(project.scenarios)) {
434
+ results.push({
435
+ ...scenario,
436
+ projectId,
437
+ projectPath: project.projectPath
438
+ });
439
+ }
440
+ }
441
+ } else {
442
+ // Return scenarios from current project only
443
+ if (globalIndex.projects[currentProjectId]) {
444
+ for (const scenario of Object.values(globalIndex.projects[currentProjectId].scenarios)) {
445
+ results.push({
446
+ ...scenario,
447
+ projectId: currentProjectId,
448
+ projectPath: globalIndex.projects[currentProjectId].projectPath
449
+ });
450
+ }
451
+ }
452
+ }
453
+
454
+ return results;
245
455
  }
246
456
 
247
457
  /**
248
458
  * Search scenarios by query
249
459
  * @param {Object} query - Search query { tags?, text?, dependencies? }
250
- * @param {string} baseDir - Base directory for storage
460
+ * @param {string} currentProjectId - Current project identifier
461
+ * @param {boolean} allProjects - Whether to search in all projects
251
462
  * @returns {Array} - Matching scenarios
252
463
  */
253
- export async function searchScenarios(query, baseDir) {
254
- const scenariosDir = path.join(baseDir, 'scenarios');
255
- const index = await loadIndex(scenariosDir);
256
- const scenarios = Object.values(index);
464
+ export async function searchScenarios(query, currentProjectId, allProjects = false) {
465
+ // Get all scenarios based on allProjects flag
466
+ const scenarios = await listScenarios(currentProjectId, allProjects);
257
467
 
258
468
  let results = scenarios;
259
469
 
@@ -286,13 +496,22 @@ export async function searchScenarios(query, baseDir) {
286
496
  /**
287
497
  * Delete scenario
288
498
  * @param {string} name - Scenario name
289
- * @param {string} baseDir - Base directory for storage
499
+ * @param {string|null} projectId - Optional project ID filter
290
500
  * @returns {boolean} - Success
291
501
  */
292
- export async function deleteScenario(name, baseDir) {
502
+ export async function deleteScenario(name, projectId = null) {
293
503
  try {
294
- const scenariosDir = path.join(baseDir, 'scenarios');
295
- const secretsDir = path.join(baseDir, 'secrets');
504
+ // Find scenario in global index
505
+ const result = findScenarioInGlobalIndex(name, projectId);
506
+
507
+ if (!result) {
508
+ console.error(`Scenario "${name}" not found${projectId ? ` in project "${projectId}"` : ''}`);
509
+ return false;
510
+ }
511
+
512
+ const projectDir = path.join(PROJECTS_DIR, result.projectId);
513
+ const scenariosDir = path.join(projectDir, 'scenarios');
514
+ const secretsDir = path.join(projectDir, 'secrets');
296
515
 
297
516
  // Delete scenario file
298
517
  const scenarioPath = path.join(scenariosDir, `${name}.json`);
@@ -306,11 +525,14 @@ export async function deleteScenario(name, baseDir) {
306
525
  // Secrets file may not exist
307
526
  }
308
527
 
309
- // Remove from index
528
+ // Remove from project-local index
310
529
  const index = await loadIndex(scenariosDir);
311
530
  delete index[name];
312
531
  await saveIndex(index, scenariosDir);
313
532
 
533
+ // Remove from global index
534
+ await removeFromGlobalIndex(result.projectId, name);
535
+
314
536
  return true;
315
537
  } catch (error) {
316
538
  console.error(`Error deleting scenario "${name}":`, error.message);