@muggleai/works 4.7.0 → 4.8.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.
package/README.md CHANGED
@@ -55,7 +55,9 @@ This installs:
55
55
  npm install -g @muggleai/works
56
56
  ```
57
57
 
58
- Then configure your MCP client:
58
+ For Cursor, that's it — the install automatically configures `~/.cursor/mcp.json` and syncs `muggle-*` skills to `~/.cursor/skills/`. Just restart Cursor.
59
+
60
+ For other MCP clients, add this to your client's config:
59
61
 
60
62
  ```json
61
63
  {
@@ -71,7 +73,7 @@ Then configure your MCP client:
71
73
  }
72
74
  ```
73
75
 
74
- `npm install` also syncs `muggle-*` skills to `~/.cursor/skills/` for Cursor discovery. Claude slash commands are plugin-managed, so update those with `/plugin update muggleai@muggle-works`.
76
+ Claude slash commands are plugin-managed, so update those with `/plugin update muggleai@muggle-works`.
75
77
 
76
78
  ### 2. Verify
77
79
 
@@ -519,7 +521,7 @@ muggle-ai-works/
519
521
  │ ├── verify-plugin-marketplace.mjs # Validates plugin/marketplace consistency
520
522
  │ ├── verify-compatibility-contracts.mjs # Validates long-term surface contracts
521
523
  │ ├── verify-upgrade-experience.mjs # Validates in-place upgrade behavior
522
- │ └── postinstall.mjs # npm postinstall (Electron app download)
524
+ │ └── postinstall.mjs # npm postinstall (Electron app download, Cursor MCP config, skills sync)
523
525
 
524
526
  ├── config/compatibility/ # Contract baselines (CLI/MCP/plugin/skills)
525
527
  ├── bin/ # CLI entrypoint (muggle.js → dist/cli.js)
@@ -554,12 +556,7 @@ CI/CD and publishing
554
556
  | `verify-end-user-upgrade.yml` | Weekly + manual | Existing-user upgrade validation (cleanup + re-download + health checks) |
555
557
  | `publish-works-to-npm.yml` | Tag `v*` or manual | Verify (including release checksums), audit, smoke-install, publish to npm |
556
558
 
557
-
558
- ```bash
559
- git tag v<version> && git push --tags
560
- # publish-works-to-npm.yml handles the rest
561
- ```
562
-
559
+ **Publishing `@muggleai/works`:** use the repo-level skill **`plugin/skills/muggle-works-npm-release/SKILL.md`** (bump + `pnpm run sync:versions`, local verify, `chore(release)` PR, merge, then `workflow_dispatch` with an explicit `version`). Do not rely on tagging alone while `package.json` / marketplace manifests on `master` are still old — CI can publish a version that does not match the checked-in manifests. Tag `v*` push remains a valid workflow trigger when it matches the merged release commit.
563
560
 
564
561
  Release tag strategy
565
562
 
@@ -2211,6 +2211,9 @@ async function executeElectronAppAsync(params) {
2211
2211
  if (params.showUi) {
2212
2212
  spawnArgs.push("--show-ui");
2213
2213
  }
2214
+ if (params.freshSession) {
2215
+ spawnArgs.push("--fresh-session");
2216
+ }
2214
2217
  logger4.info("Spawning electron-app for local execution", {
2215
2218
  runId: params.runId,
2216
2219
  mode,
@@ -2359,7 +2362,8 @@ async function executeTestGeneration(params) {
2359
2362
  scriptFilePath: inputFilePath,
2360
2363
  authFilePath,
2361
2364
  timeoutMs,
2362
- showUi: params.showUi
2365
+ showUi: params.showUi,
2366
+ freshSession: params.freshSession
2363
2367
  });
2364
2368
  const completedAt = Date.now();
2365
2369
  const executionTimeMs = completedAt - startedAt;
@@ -2407,9 +2411,11 @@ ${executionResult.stderr}`;
2407
2411
  `Generated script does not contain a valid 'steps' array. File: ${generatedScriptPath}`
2408
2412
  );
2409
2413
  }
2414
+ const generatedSummaryStep = generatedScript.summaryStep;
2410
2415
  storage.updateTestScript(localTestScript.id, {
2411
2416
  status: "generated",
2412
- actionScript: generatedSteps
2417
+ actionScript: generatedSteps,
2418
+ summaryStep: generatedSummaryStep
2413
2419
  });
2414
2420
  const artifactsDir = await moveResultsToArtifacts({
2415
2421
  runId,
@@ -2503,7 +2509,8 @@ async function executeReplay(params) {
2503
2509
  scriptFilePath: inputFilePath,
2504
2510
  authFilePath,
2505
2511
  timeoutMs,
2506
- showUi: params.showUi
2512
+ showUi: params.showUi,
2513
+ freshSession: params.freshSession
2507
2514
  });
2508
2515
  const completedAt = Date.now();
2509
2516
  const executionTimeMs = completedAt - startedAt;
@@ -3237,6 +3244,12 @@ var WorkflowStartTestScriptGenerationInputSchema = z.object({
3237
3244
  expectedResult: z.string().min(1).describe("Expected result"),
3238
3245
  workflowParams: WorkflowParamsSchema
3239
3246
  });
3247
+ var WorkflowStartTestScriptGenerationBulkInputSchema = z.object({
3248
+ projectId: IdSchema.describe("Project ID (UUID)"),
3249
+ name: z.string().min(1).describe("Workflow name"),
3250
+ testCaseIds: z.array(IdSchema).optional().describe("Optional: targeted test case UUIDs to generate scripts for; when absent generates for all eligible test cases in the project"),
3251
+ workflowParams: WorkflowParamsSchema
3252
+ });
3240
3253
  var WorkflowGetLatestScriptGenByTestCaseInputSchema = z.object({
3241
3254
  testCaseId: IdSchema.describe("Test case ID (UUID)")
3242
3255
  });
@@ -4206,6 +4219,25 @@ var workflowTools = [
4206
4219
  };
4207
4220
  }
4208
4221
  },
4222
+ {
4223
+ name: "muggle-remote-workflow-start-test-script-generation-bulk",
4224
+ description: "Start a bulk test script generation workflow to generate scripts for multiple test cases in a single request.",
4225
+ inputSchema: WorkflowStartTestScriptGenerationBulkInputSchema,
4226
+ mapToUpstream: (input) => {
4227
+ const data = input;
4228
+ return {
4229
+ method: "POST",
4230
+ path: `${MUGGLE_TEST_PREFIX}/workflow/test-script/test-script-generation/bulk`,
4231
+ body: {
4232
+ projectId: data.projectId,
4233
+ name: data.name,
4234
+ ...data.testCaseIds && { testCaseIds: data.testCaseIds },
4235
+ ...data.workflowParams && { workflowParams: data.workflowParams }
4236
+ },
4237
+ timeoutMs: getWorkflowTimeoutMs()
4238
+ };
4239
+ }
4240
+ },
4209
4241
  {
4210
4242
  name: "muggle-remote-wf-get-ts-gen-latest-run",
4211
4243
  description: "Get the latest run status for a test script generation workflow runtime.",
@@ -5144,14 +5176,6 @@ __export(local_exports, {
5144
5176
  executeTool: () => executeTool,
5145
5177
  getTool: () => getTool
5146
5178
  });
5147
- var AuthLoginInputSchema2 = z.object({
5148
- waitForCompletion: z.boolean().optional().describe("Whether to wait for browser login completion before returning. Default: true"),
5149
- timeoutMs: z.number().int().positive().min(1e3).max(9e5).optional().describe("Maximum time to wait for login completion in milliseconds. Default: 120000")
5150
- });
5151
- var AuthPollInputSchema2 = z.object({
5152
- deviceCode: z.string().optional().describe("Device code from the login response. Optional if a login was recently started.")
5153
- });
5154
- var EmptyInputSchema2 = z.object({});
5155
5179
  var TestCaseDetailsSchema = z.object({
5156
5180
  /** Cloud test case ID. */
5157
5181
  id: MuggleEntityIdSchema.describe("Cloud test case ID (UUID)"),
@@ -5198,7 +5222,9 @@ var ExecuteTestGenerationInputSchema = z.object({
5198
5222
  /** Optional timeout. */
5199
5223
  timeoutMs: z.number().int().positive().optional().describe("Timeout in milliseconds (default: 300000 = 5 min)"),
5200
5224
  /** Show the electron-app UI during execution. Default: visible window. Pass false to run headless. */
5201
- showUi: z.boolean().optional().describe("Show the electron-app UI during generation. Defaults to visible; pass false to run headless.")
5225
+ showUi: z.boolean().optional().describe("Show the electron-app UI during generation. Defaults to visible; pass false to run headless."),
5226
+ /** Clear all session storage (cookies, localStorage, etc.) before execution. Use for test cases that require a clean browser state, such as registration, login, or cookie consent flows. */
5227
+ freshSession: z.boolean().optional().describe("Clear all session storage (cookies, localStorage, etc.) before execution. Use for test cases that require a clean browser state \u2014 e.g. registration, login, or cookie consent flows. Default: false.")
5202
5228
  });
5203
5229
  var ExecuteReplayInputSchema = z.object({
5204
5230
  /** Test script metadata from muggle-remote-test-script-get. */
@@ -5210,7 +5236,9 @@ var ExecuteReplayInputSchema = z.object({
5210
5236
  /** Optional timeout. */
5211
5237
  timeoutMs: z.number().int().positive().optional().describe("Timeout in milliseconds (default: 180000 = 3 min)"),
5212
5238
  /** Show the electron-app UI during execution. Default: visible window. Pass false to run headless. */
5213
- showUi: z.boolean().optional().describe("Show the electron-app UI during replay. Defaults to visible; pass false to run headless.")
5239
+ showUi: z.boolean().optional().describe("Show the electron-app UI during replay. Defaults to visible; pass false to run headless."),
5240
+ /** Clear all session storage (cookies, localStorage, etc.) before execution. Use for test cases that require a clean browser state, such as registration, login, or cookie consent flows. */
5241
+ freshSession: z.boolean().optional().describe("Clear all session storage (cookies, localStorage, etc.) before execution. Use for test cases that require a clean browser state \u2014 e.g. registration, login, or cookie consent flows. Default: false.")
5214
5242
  });
5215
5243
  var CancelExecutionInputSchema = z.object({
5216
5244
  runId: MuggleEntityIdSchema.describe("Run ID (UUID) to cancel")
@@ -5238,6 +5266,7 @@ var ListSessionsInputSchema = z.object({
5238
5266
  var CleanupSessionsInputSchema = z.object({
5239
5267
  max_age_days: z.number().int().min(0).optional().describe("Maximum age of sessions to keep (in days). Sessions older than this will be deleted. Defaults to 30.")
5240
5268
  });
5269
+ var EmptyInputSchema2 = z.object({});
5241
5270
 
5242
5271
  // packages/mcps/src/mcp/tools/local/tool-registry.ts
5243
5272
  function createChildLogger2(correlationId) {
@@ -5462,7 +5491,8 @@ var executeTestGenerationTool = {
5462
5491
  testCase: input.testCase,
5463
5492
  localUrl: input.localUrl,
5464
5493
  timeoutMs: input.timeoutMs,
5465
- showUi
5494
+ showUi,
5495
+ freshSession: input.freshSession
5466
5496
  });
5467
5497
  const content = [
5468
5498
  "## Test Generation " + (result.status === "passed" ? "Successful" : "Failed"),
@@ -5501,7 +5531,8 @@ var executeReplayTool = {
5501
5531
  actionScript: input.actionScript,
5502
5532
  localUrl: input.localUrl,
5503
5533
  timeoutMs: input.timeoutMs,
5504
- showUi
5534
+ showUi,
5535
+ freshSession: input.freshSession
5505
5536
  });
5506
5537
  const content = [
5507
5538
  "## Test Replay " + (result.status === "passed" ? "Successful" : "Failed"),
@@ -5629,6 +5660,7 @@ var publishTestScriptTool = {
5629
5660
  uploadedAt
5630
5661
  },
5631
5662
  actionScript: testScript.actionScript,
5663
+ summaryStep: testScript.summaryStep,
5632
5664
  status: runResult.status === "passed" ? "passed" : "failed",
5633
5665
  executionTimeMs: runResult.executionTimeMs,
5634
5666
  errorMessage: runResult.errorMessage
@@ -5796,6 +5828,7 @@ __export(e2e_exports2, {
5796
5828
  WorkflowMemoryParamsSchema: () => WorkflowMemoryParamsSchema,
5797
5829
  WorkflowParamsSchema: () => WorkflowParamsSchema,
5798
5830
  WorkflowStartTestCaseDetectionInputSchema: () => WorkflowStartTestCaseDetectionInputSchema,
5831
+ WorkflowStartTestScriptGenerationBulkInputSchema: () => WorkflowStartTestScriptGenerationBulkInputSchema,
5799
5832
  WorkflowStartTestScriptGenerationInputSchema: () => WorkflowStartTestScriptGenerationInputSchema,
5800
5833
  WorkflowStartTestScriptReplayBulkInputSchema: () => WorkflowStartTestScriptReplayBulkInputSchema,
5801
5834
  WorkflowStartTestScriptReplayInputSchema: () => WorkflowStartTestScriptReplayInputSchema,
@@ -5821,8 +5854,6 @@ function getQaTools() {
5821
5854
  // packages/mcps/src/mcp/local/index.ts
5822
5855
  var local_exports2 = {};
5823
5856
  __export(local_exports2, {
5824
- AuthLoginInputSchema: () => AuthLoginInputSchema2,
5825
- AuthPollInputSchema: () => AuthPollInputSchema2,
5826
5857
  AuthService: () => AuthService,
5827
5858
  CancelExecutionInputSchema: () => CancelExecutionInputSchema,
5828
5859
  CleanupSessionsInputSchema: () => CleanupSessionsInputSchema,
@@ -1,4 +1,4 @@
1
- import { __export, getLogger, getConfig, createChildLogger, buildElectronAppReleaseAssetUrl, getAuthService, hasApiKey, getElectronAppVersion, getElectronAppDir, getPlatformKey, isElectronAppInstalled, getElectronAppChecksums, getChecksumForPlatform, verifyFileChecksum, calculateFileChecksum, getQaTools, getLocalQaTools, performLogout, performLogin, toolRequiresAuth, getCallerCredentials, getDataDir, getBundledElectronAppVersion, getElectronAppVersionSource, getCredentialsFilePath, buildElectronAppChecksumsUrl, __require } from './chunk-HDEZDEM6.js';
1
+ import { __export, getLogger, getConfig, createChildLogger, buildElectronAppReleaseAssetUrl, getAuthService, hasApiKey, getElectronAppVersion, getElectronAppDir, getPlatformKey, isElectronAppInstalled, getElectronAppChecksums, getChecksumForPlatform, verifyFileChecksum, calculateFileChecksum, getQaTools, getLocalQaTools, performLogout, performLogin, toolRequiresAuth, getCallerCredentials, getDataDir, getBundledElectronAppVersion, getElectronAppVersionSource, getCredentialsFilePath, buildElectronAppChecksumsUrl, __require } from './chunk-44I5ROCB.js';
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
4
  import { v4 } from 'uuid';
@@ -6,12 +6,12 @@ import { z, ZodError } from 'zod';
6
6
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
7
  import * as fs from 'fs';
8
8
  import { readFileSync, existsSync, rmSync, mkdirSync, readdirSync, createWriteStream, writeFileSync, statSync } from 'fs';
9
- import * as path from 'path';
9
+ import * as path4 from 'path';
10
10
  import { dirname, resolve, join } from 'path';
11
11
  import { fileURLToPath } from 'url';
12
12
  import { Command } from 'commander';
13
13
  import axios from 'axios';
14
- import { platform, arch, homedir } from 'os';
14
+ import { platform, homedir, arch } from 'os';
15
15
  import { execFile } from 'child_process';
16
16
  import { pipeline } from 'stream/promises';
17
17
 
@@ -486,7 +486,7 @@ function renderResultSummary(test, projectId) {
486
486
  lines.push(`**Error:** \`${safeInlineCode(test.error)}\``);
487
487
  }
488
488
  lines.push(`**Steps:** ${test.steps.length}`);
489
- lines.push(`[View on Muggle AI dashboard \u2192](${dashboardUrl})`);
489
+ lines.push(`[View steps on Muggle AI \u2192](${dashboardUrl})`);
490
490
  return lines;
491
491
  }
492
492
  function renderBody(report, opts) {
@@ -686,7 +686,7 @@ async function resolveGsScreenshotUrls(report, opts) {
686
686
  if (gsUrls.length === 0) {
687
687
  return report;
688
688
  }
689
- const mcps = await import('./src-TX2KXI26.js');
689
+ const mcps = await import('./src-ZRUONWKV.js');
690
690
  const credentials = await mcps.getCallerCredentialsAsync();
691
691
  if (!credentials.bearerToken && !credentials.apiKey) {
692
692
  stderrWrite(
@@ -807,13 +807,13 @@ var CURSOR_SKILLS_SUBDIR = "skills";
807
807
  var MUGGLE_SKILL_PREFIX = "muggle";
808
808
  var INSTALL_MANIFEST_FILE = "install-manifest.json";
809
809
  function getElectronAppBaseDir() {
810
- return path.join(getDataDir(), ELECTRON_APP_DIR);
810
+ return path4.join(getDataDir(), ELECTRON_APP_DIR);
811
811
  }
812
812
  function getCursorSkillsDir() {
813
- return path.join(homedir(), CURSOR_SKILLS_DIR, CURSOR_SKILLS_SUBDIR);
813
+ return path4.join(homedir(), CURSOR_SKILLS_DIR, CURSOR_SKILLS_SUBDIR);
814
814
  }
815
815
  function getInstallManifestPath() {
816
- return path.join(getDataDir(), INSTALL_MANIFEST_FILE);
816
+ return path4.join(getDataDir(), INSTALL_MANIFEST_FILE);
817
817
  }
818
818
  function readInstallManifest() {
819
819
  const manifestPath = getInstallManifestPath();
@@ -851,7 +851,7 @@ function listObsoleteSkills() {
851
851
  if (manifestSkills.has(entry.name)) {
852
852
  continue;
853
853
  }
854
- const skillPath = path.join(skillsDir, entry.name);
854
+ const skillPath = path4.join(skillsDir, entry.name);
855
855
  const sizeBytes = getDirectorySize(skillPath);
856
856
  obsoleteSkills.push({
857
857
  name: entry.name,
@@ -897,7 +897,7 @@ function getDirectorySize(dirPath) {
897
897
  try {
898
898
  const entries = readdirSync(dirPath, { withFileTypes: true });
899
899
  for (const entry of entries) {
900
- const fullPath = path.join(dirPath, entry.name);
900
+ const fullPath = path4.join(dirPath, entry.name);
901
901
  if (entry.isDirectory()) {
902
902
  totalSize += getDirectorySize(fullPath);
903
903
  } else if (entry.isFile()) {
@@ -950,7 +950,7 @@ function listInstalledVersions() {
950
950
  if (!/^\d+\.\d+\.\d+$/.test(entry.name)) {
951
951
  continue;
952
952
  }
953
- const versionPath = path.join(baseDir, entry.name);
953
+ const versionPath = path4.join(baseDir, entry.name);
954
954
  const sizeBytes = getDirectorySize(versionPath);
955
955
  versions.push({
956
956
  version: entry.name,
@@ -1106,11 +1106,11 @@ function getExpectedExecutablePath(versionDir) {
1106
1106
  const os = platform();
1107
1107
  switch (os) {
1108
1108
  case "darwin":
1109
- return path.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
1109
+ return path4.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
1110
1110
  case "win32":
1111
- return path.join(versionDir, "MuggleAI.exe");
1111
+ return path4.join(versionDir, "MuggleAI.exe");
1112
1112
  case "linux":
1113
- return path.join(versionDir, "MuggleAI");
1113
+ return path4.join(versionDir, "MuggleAI");
1114
1114
  default:
1115
1115
  throw new Error(`Unsupported platform: ${os}`);
1116
1116
  }
@@ -1119,7 +1119,7 @@ function verifyElectronAppInstallation() {
1119
1119
  const version = getElectronAppVersion();
1120
1120
  const versionDir = getElectronAppDir(version);
1121
1121
  const executablePath = getExpectedExecutablePath(versionDir);
1122
- const metadataPath = path.join(versionDir, ".install-metadata.json");
1122
+ const metadataPath = path4.join(versionDir, ".install-metadata.json");
1123
1123
  const result = {
1124
1124
  valid: false,
1125
1125
  versionDir,
@@ -1326,7 +1326,7 @@ function runDiagnostics() {
1326
1326
  name: "Cursor MCP Config",
1327
1327
  passed: cursorMcpConfigValidationResult.passed,
1328
1328
  description: cursorMcpConfigValidationResult.description,
1329
- suggestion: "Re-run npm install -g @muggleai/works to refresh ~/.cursor/mcp.json"
1329
+ suggestion: "Run 'muggle setup' to configure ~/.cursor/mcp.json"
1330
1330
  });
1331
1331
  return results;
1332
1332
  }
@@ -1640,11 +1640,11 @@ function getExpectedExecutablePath2(versionDir) {
1640
1640
  const os = platform();
1641
1641
  switch (os) {
1642
1642
  case "darwin":
1643
- return path.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
1643
+ return path4.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
1644
1644
  case "win32":
1645
- return path.join(versionDir, "MuggleAI.exe");
1645
+ return path4.join(versionDir, "MuggleAI.exe");
1646
1646
  case "linux":
1647
- return path.join(versionDir, "MuggleAI");
1647
+ return path4.join(versionDir, "MuggleAI");
1648
1648
  default:
1649
1649
  throw new Error(`Unsupported platform: ${os}`);
1650
1650
  }
@@ -1740,10 +1740,43 @@ function cleanupFailedInstall(versionDir) {
1740
1740
  }
1741
1741
  }
1742
1742
  }
1743
+ function upsertCursorMcpConfig() {
1744
+ const cursorMcpConfigPath = path4.join(homedir(), ".cursor", "mcp.json");
1745
+ const cursorDir = path4.join(homedir(), ".cursor");
1746
+ let config = {};
1747
+ if (existsSync(cursorMcpConfigPath)) {
1748
+ try {
1749
+ const raw = readFileSync(cursorMcpConfigPath, "utf-8");
1750
+ const parsed = JSON.parse(raw);
1751
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1752
+ console.log("Warning: ~/.cursor/mcp.json has unexpected shape, skipping MCP config upsert.");
1753
+ return;
1754
+ }
1755
+ config = parsed;
1756
+ } catch (error) {
1757
+ const message = error instanceof Error ? error.message : String(error);
1758
+ console.log("Warning: ~/.cursor/mcp.json is invalid JSON, skipping MCP config upsert.");
1759
+ console.log(` Parse error: ${message}`);
1760
+ return;
1761
+ }
1762
+ }
1763
+ if (!config.mcpServers || typeof config.mcpServers !== "object") {
1764
+ config.mcpServers = {};
1765
+ }
1766
+ config.mcpServers.muggle = {
1767
+ command: "muggle",
1768
+ args: ["serve"]
1769
+ };
1770
+ mkdirSync(cursorDir, { recursive: true });
1771
+ writeFileSync(cursorMcpConfigPath, `${JSON.stringify(config, null, 2)}
1772
+ `, "utf-8");
1773
+ console.log(`Cursor MCP config updated at ${cursorMcpConfigPath}`);
1774
+ }
1743
1775
  async function setupCommand(options) {
1744
1776
  const version = getElectronAppVersion();
1745
1777
  const versionDir = getElectronAppDir(version);
1746
1778
  const platformKey = getPlatformKey();
1779
+ upsertCursorMcpConfig();
1747
1780
  if (!options.force && isElectronAppInstalled()) {
1748
1781
  console.log(`Electron app v${version} is already installed at ${versionDir}`);
1749
1782
  console.log("Use --force to re-download.");
@@ -1761,7 +1794,7 @@ async function setupCommand(options) {
1761
1794
  rmSync(versionDir, { recursive: true, force: true });
1762
1795
  }
1763
1796
  mkdirSync(versionDir, { recursive: true });
1764
- const tempFile = path.join(versionDir, binaryName);
1797
+ const tempFile = path4.join(versionDir, binaryName);
1765
1798
  await downloadWithRetry(downloadUrl, tempFile);
1766
1799
  console.log("Download complete, verifying checksum...");
1767
1800
  const checksums = getElectronAppChecksums();
@@ -1797,7 +1830,7 @@ The archive may be corrupted or in an unexpected format.`
1797
1830
  );
1798
1831
  }
1799
1832
  const executableChecksum = await calculateFileChecksum(executablePath);
1800
- const metadataPath = path.join(versionDir, INSTALL_METADATA_FILE_NAME);
1833
+ const metadataPath = path4.join(versionDir, INSTALL_METADATA_FILE_NAME);
1801
1834
  writeInstallMetadata({
1802
1835
  metadataPath,
1803
1836
  version,
@@ -1839,7 +1872,7 @@ function extractVersionFromTag(tag) {
1839
1872
  return match ? match[1] : null;
1840
1873
  }
1841
1874
  function getVersionOverridePath() {
1842
- return path.join(getDataDir(), VERSION_OVERRIDE_FILE);
1875
+ return path4.join(getDataDir(), VERSION_OVERRIDE_FILE);
1843
1876
  }
1844
1877
  function getEffectiveElectronAppVersion() {
1845
1878
  const overridePath = getVersionOverridePath();
@@ -1927,11 +1960,11 @@ function getExpectedExecutablePath3(versionDir) {
1927
1960
  const os = platform();
1928
1961
  switch (os) {
1929
1962
  case "darwin":
1930
- return path.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
1963
+ return path4.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
1931
1964
  case "win32":
1932
- return path.join(versionDir, "MuggleAI.exe");
1965
+ return path4.join(versionDir, "MuggleAI.exe");
1933
1966
  case "linux":
1934
- return path.join(versionDir, "MuggleAI");
1967
+ return path4.join(versionDir, "MuggleAI");
1935
1968
  default:
1936
1969
  throw new Error(`Unsupported platform: ${os}`);
1937
1970
  }
@@ -2043,7 +2076,7 @@ async function downloadAndInstall(version, downloadUrl, checksum) {
2043
2076
  if (!response.ok) {
2044
2077
  throw new Error(`Download failed: ${response.status} ${response.statusText}`);
2045
2078
  }
2046
- const tempFile = path.join(versionDir, binaryName);
2079
+ const tempFile = path4.join(versionDir, binaryName);
2047
2080
  const fileStream = createWriteStream(tempFile);
2048
2081
  if (!response.body) {
2049
2082
  throw new Error("No response body");
@@ -2085,7 +2118,7 @@ The archive may be corrupted or in an unexpected format.`
2085
2118
  );
2086
2119
  }
2087
2120
  const executableChecksum = await calculateFileChecksum(executablePath);
2088
- const metadataPath = path.join(versionDir, INSTALL_METADATA_FILE_NAME2);
2121
+ const metadataPath = path4.join(versionDir, INSTALL_METADATA_FILE_NAME2);
2089
2122
  writeInstallMetadata2({
2090
2123
  metadataPath,
2091
2124
  version,
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './chunk-SMRMPHDD.js';
3
- import './chunk-HDEZDEM6.js';
2
+ import { runCli } from './chunk-OMLNCNSZ.js';
3
+ import './chunk-44I5ROCB.js';
4
4
 
5
5
  // src/cli/main.ts
6
6
  runCli().catch((error) => {
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-SMRMPHDD.js';
2
- export { createChildLogger, e2e_exports as e2e, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, e2e_exports as qa, src_exports as shared } from './chunk-HDEZDEM6.js';
1
+ export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-OMLNCNSZ.js';
2
+ export { createChildLogger, e2e_exports as e2e, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, e2e_exports as qa, src_exports as shared } from './chunk-44I5ROCB.js';
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "muggle",
3
3
  "description": "Run real-browser end-to-end (E2E) acceptance tests on your web app from any AI coding agent. Generate test scripts from plain English, replay them on localhost, capture screenshots, and validate user flows like signup, checkout, and dashboards. Works across Claude Code, Cursor, Codex, and Windsurf.",
4
- "version": "4.7.0",
4
+ "version": "4.8.1",
5
5
  "author": {
6
6
  "name": "Muggle AI",
7
7
  "email": "support@muggle-ai.com"
@@ -2,7 +2,7 @@
2
2
  "name": "muggle",
3
3
  "displayName": "Muggle AI",
4
4
  "description": "Ship quality products with AI-powered end-to-end (E2E) acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
5
- "version": "4.7.0",
5
+ "version": "4.8.1",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -15,7 +15,7 @@ For npm installs:
15
15
  npm install -g @muggleai/works
16
16
  ```
17
17
 
18
- This updates the CLI and syncs `muggle-*` skills into `~/.cursor/skills/` for Cursor. Claude slash commands remain plugin-managed, so use `/plugin update muggleai@muggle-works` to refresh them.
18
+ This updates the CLI, configures Cursor MCP (`~/.cursor/mcp.json`), and syncs `muggle-*` skills into `~/.cursor/skills/`. Claude slash commands remain plugin-managed, so use `/plugin update muggleai@muggle-works` to refresh them.
19
19
 
20
20
  ## Skills
21
21
 
@@ -200,6 +200,17 @@ If nothing detected, ask as free text: "Your local app should be running. What's
200
200
 
201
201
  Before execution, fetch full test case details for all selected test cases by issuing **all** `muggle-remote-test-case-get` calls in parallel (single message, multiple tool calls).
202
202
 
203
+ ### Determine `freshSession` per test case
204
+
205
+ Before executing each test case, inspect its content (title, goal, instructions, preconditions) for signals that it requires a **clean browser state** — no prior cookies, localStorage, or logged-in session. Set `freshSession: true` when the test case involves any of:
206
+
207
+ - **Registration / sign-up** — creating a new account
208
+ - **Login / authentication** — verifying the login flow itself (not a test that merely *uses* login as a prerequisite)
209
+ - **Cookie consent / GDPR banners** — verifying first-visit consent prompts
210
+ - **Onboarding flows** — first-time user experiences that only appear on a fresh session
211
+
212
+ If none of the above apply, omit `freshSession` (defaults to `false`, preserving any existing session state). Evaluate this per test case — in a batch, some may need it and others may not.
213
+
203
214
  ### Run sequentially (Electron constraint)
204
215
 
205
216
  Execution itself **must** be sequential because there is only one local Electron browser. For each test case, in order:
@@ -208,6 +219,7 @@ Execution itself **must** be sequential because there is only one local Electron
208
219
  - `testCase`: Full test case object from the parallel fetch above
209
220
  - `localUrl`: User's local URL from the pre-flight question
210
221
  - `showUi`: omit (default visible) unless the user explicitly asked for headless, then pass `false`
222
+ - `freshSession`: `true` if the test case requires a clean browser state (see above), omit otherwise
211
223
  2. Store the returned `runId`
212
224
 
213
225
  If a generation fails, log it and continue to the next. Do not abort the batch.
@@ -231,7 +243,7 @@ Store every `viewUrl` — these are used in the next steps.
231
243
  ### Report summary
232
244
 
233
245
  ```
234
- Test Case Status Duration Steps View on Muggle
246
+ Test Case Status Duration Steps View Steps on Muggle AI
235
247
  ─────────────────────────────────────────────────────────────────────────
236
248
  Login with valid creds PASSED 12.3s 8 https://www.muggle-ai.com/...
237
249
  Login with invalid creds PASSED 9.1s 6 https://www.muggle-ai.com/...
@@ -86,17 +86,28 @@ Remind them: local URL is only the execution target, not tied to cloud project c
86
86
 
87
87
  ### 5. Load data for the chosen path
88
88
 
89
+ **Determine `freshSession`**
90
+
91
+ Before calling either execution tool, inspect the test case content (title, goal, instructions, preconditions) for signals that the test requires a **clean browser state** — no prior cookies, localStorage, or logged-in session. Pass `freshSession: true` when the test case involves any of:
92
+
93
+ - **Registration / sign-up** — creating a new account
94
+ - **Login / authentication** — verifying the login flow itself (not a test that merely *uses* login as a prerequisite)
95
+ - **Cookie consent / GDPR banners** — verifying first-visit consent prompts
96
+ - **Onboarding flows** — first-time user experiences that only appear on a fresh session
97
+
98
+ If none of the above apply, omit `freshSession` (defaults to `false`, preserving any existing session state).
99
+
89
100
  **Generate**
90
101
 
91
102
  1. `muggle-remote-test-case-get`
92
- 2. `muggle-local-execute-test-generation` with that test case + `localUrl` (optional: `showUi: false` for headless — defaults to visible; **`timeoutMs`** — see below)
103
+ 2. `muggle-local-execute-test-generation` with that test case + `localUrl` (optional: `showUi: false` for headless — defaults to visible; **`freshSession`** — see above; **`timeoutMs`** — see below)
93
104
 
94
105
  **Replay**
95
106
 
96
107
  1. `muggle-remote-test-script-get` — note `actionScriptId`
97
108
  2. `muggle-remote-action-script-get` with that id — full `actionScript`
98
109
  **Use the API response as-is.** Do not edit, shorten, or rebuild `actionScript`; replay needs full `label` paths for element lookup.
99
- 3. `muggle-local-execute-replay` with `testScript`, `actionScript`, `localUrl` (optional: `showUi: false` for headless — defaults to visible; **`timeoutMs`** — see below)
110
+ 3. `muggle-local-execute-replay` with `testScript`, `actionScript`, `localUrl` (optional: `showUi: false` for headless — defaults to visible; **`freshSession`** — see above; **`timeoutMs`** — see below)
100
111
 
101
112
  ### Local execution timeout (`timeoutMs`)
102
113
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: muggle-test-regenerate-missing
3
- description: "Bulk-regenerate test scripts for every test case in a Muggle AI project that doesn't currently have an active script. Scans the project, finds test cases stuck in DRAFT or GENERATION_PENDING (no usable script attached), shows the user the list, and on approval kicks off remote test script generation for each one in parallel via the Muggle cloud. Use this skill whenever the user asks to 'regenerate missing scripts', 'fill in missing test scripts', 'generate scripts for test cases without one', 'regen all the test cases that don't have scripts', 'rebuild scripts for stale test cases', 'fix test cases with no script', 'bulk regenerate', or any phrasing that means 'kick off script generation across a project for the cases that need it'. Triggers on: 'regenerate missing test scripts', 'generate scripts for all empty test cases', 'fill the gaps in my test scripts', 'bulk test script regen', 'all my test cases without active scripts'. This is the go-to skill for project-wide script catch-up — it handles discovery, filtering, confirmation, and remote workflow dispatch end-to-end."
3
+ description: "Bulk-regenerate test scripts for every test case in a Muggle AI project that doesn't currently have an active script. Scans the project, finds test cases stuck in DRAFT or GENERATION_PENDING (no usable script attached), shows the user the list, and on approval kicks off bulk remote test script generation via the Muggle cloud. Use this skill whenever the user asks to 'regenerate missing scripts', 'fill in missing test scripts', 'generate scripts for test cases without one', 'regen all the test cases that don't have scripts', 'rebuild scripts for stale test cases', 'fix test cases with no script', 'bulk regenerate', or any phrasing that means 'kick off script generation across a project for the cases that need it'. Triggers on: 'regenerate missing test scripts', 'generate scripts for all empty test cases', 'fill the gaps in my test scripts', 'bulk test script regen', 'all my test cases without active scripts'. This is the go-to skill for project-wide script catch-up — it handles discovery, filtering, confirmation, and remote workflow dispatch end-to-end."
4
4
  ---
5
5
 
6
6
  # Muggle Test — Regenerate Missing Test Scripts
@@ -116,27 +116,22 @@ After selection, call `AskQuestion` once more for a final confirmation:
116
116
 
117
117
  Only proceed after the user picks "Yes".
118
118
 
119
- ### Step 6 — Dispatch Remote Generations
119
+ ### Step 6 — Dispatch Remote Generations (Bulk)
120
120
 
121
- For each selected test case, in order:
121
+ Send a single bulk request instead of dispatching one workflow per test case:
122
122
 
123
- 1. Call `muggle-remote-test-case-get` with `testCaseId` to fetch the full record (the list endpoint returns a slim shape; generation needs `goal`, `precondition`, `instructions`, `expectedResult`, `url`).
124
- 2. Call `muggle-remote-workflow-start-test-script-generation` with:
123
+ 1. Call `muggle-remote-workflow-start-test-script-generation-bulk` with:
125
124
  - `projectId` — from Step 2
126
- - `useCaseId` — from the test case
127
- - `testCaseId` — the test case being regenerated
128
- - `name` `"muggle-test-regenerate-missing: {test case title}"` (so it's easy to find this batch later in the dashboard)
129
- - `url` prefer the test case's own `url` if set, else the project URL from Step 2
130
- - `goal`, `precondition`, `instructions`, `expectedResult` — straight from the test case. If `precondition` is empty, pass `"None"` (the schema requires a non-empty string).
131
- 3. Capture the returned workflow runtime ID and store it alongside the test case.
125
+ - `name` — `"muggle-test-regenerate-missing: bulk ({count} test cases)"` where `{count}` is the number of selected test cases
126
+ - `testCaseIds` — array of all selected test case IDs from Step 5
127
+ 2. The backend handles looking up full test case details (goal, precondition, instructions, expectedResult, url), so there is no need to call `muggle-remote-test-case-get` per test case.
128
+ 3. Parse the response to get the `items` array with per-test-case status. Each item contains the test case ID, dispatch status, and (when successful) the workflow runtime ID.
132
129
 
133
- **Failure handling:** if a single dispatch fails (validation error, server error, missing field), log it inline, mark the test case as `dispatch_failed`, and continue to the next one. Do not abort the whole batch — partial progress is more useful than nothing.
134
-
135
- **Pacing:** Muggle's cloud handles parallelism on its side, so you don't need to throttle. Just dispatch sequentially as fast as the API will accept them.
130
+ **Failure handling:** the bulk API returns per-item status in the response `items` array. Individual test cases may fail (validation error, missing field, etc.) while others succeed. Surface failures in the Step 7 report — partial progress beats no progress.
136
131
 
137
132
  ### Step 7 — Report
138
133
 
139
- After all dispatches are done, print a summary table:
134
+ After the bulk dispatch returns, build a summary table from the response `items` array. Each item contains a test case ID, dispatch status, and (when successful) a workflow runtime ID. Cross-reference with the test case list from Step 3 to fill in titles and use case names:
140
135
 
141
136
  ```
142
137
  Test Case Use Case Prev Status Dispatch Runtime
@@ -149,7 +144,7 @@ Apply expired coupon Checkout Flow GENERATION_PEND. ❌ fa
149
144
  Total: 17 dispatched | 16 started | 1 failed
150
145
  ```
151
146
 
152
- For failures: include a one-line error excerpt and (where possible) a hint at the cause (e.g., "missing instructions field — edit the test case in the dashboard, then re-run this skill").
147
+ For failures: include a one-line error excerpt from the item's error field and (where possible) a hint at the cause (e.g., "missing instructions field — edit the test case in the dashboard, then re-run this skill").
153
148
 
154
149
  ### Step 8 — Open the Dashboard
155
150
 
@@ -183,8 +178,7 @@ Add item to cart rt-ghi789 COMPLETED 12
183
178
  | Auth | `muggle-remote-auth-status`, `muggle-remote-auth-login`, `muggle-remote-auth-poll` |
184
179
  | Project | `muggle-remote-project-list`, `muggle-remote-project-create` |
185
180
  | Scan | `muggle-remote-test-case-list` (paginated) |
186
- | Detail | `muggle-remote-test-case-get` |
187
- | Dispatch | `muggle-remote-workflow-start-test-script-generation` |
181
+ | Dispatch | `muggle-remote-workflow-start-test-script-generation-bulk` |
188
182
  | Status (optional) | `muggle-remote-wf-get-ts-gen-latest-run`, `muggle-remote-wf-get-latest-ts-gen-by-tc` |
189
183
  | Browser | `open` (shell command) |
190
184
 
@@ -193,9 +187,8 @@ Add item to cart rt-ghi789 COMPLETED 12
193
187
  - **The user MUST select the project** — present projects via `AskQuestion`, never infer from cwd, repo name, or URL guesses.
194
188
  - **The user MUST approve which test cases to regenerate** — show the candidates via `AskQuestion`, let them deselect, then confirm again before any dispatch. Bulk-regenerating without approval can waste meaningful workflow budget.
195
189
  - **Default filter is `DRAFT` + `GENERATION_PENDING`** — never include `GENERATING`, `ACTIVE`, `DEPRECATED`, `ARCHIVED`, `REPLAYING`, or `REPLAY_PENDING` unless the user explicitly says so. `GENERATING` already has a workflow in flight and dispatching another races against it. `ACTIVE` test cases already have working scripts. The rest reflect deliberate user decisions or in-flight replays the skill should not interfere with.
196
- - **Use `muggle-remote-test-case-get` before each dispatch** the list endpoint returns a slim shape and generation needs the full payload.
197
- - **Failures don't abort the batch** — log and continue, then surface them at the end. Partial progress beats no progress.
198
- - **Never throttle artificially** — dispatch sequentially as fast as the API accepts. Muggle's cloud handles parallelism.
190
+ - **Use the bulk endpoint for dispatch** — call `muggle-remote-workflow-start-test-script-generation-bulk` once with all selected test case IDs rather than dispatching one-by-one. The backend resolves full test case details internally.
191
+ - **Failures don't abort the batch** — the bulk API returns per-item status. Surface failures in the report. Partial progress beats no progress.
199
192
  - **Open the dashboard, don't poll by default** — the runs page is the canonical view of progress. Only poll if the user explicitly asks.
200
193
  - **Use `AskQuestion` for every selection** — never ask the user to type a number.
201
194
  - **Can be invoked at any state** — if the user already has a project chosen in conversation context, skip Step 2 and go straight to scanning.