@muggleai/works 4.8.0 → 4.8.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.
- package/README.md +5 -3
- package/dist/{chunk-OUI734ME.js → chunk-44I5ROCB.js} +44 -16
- package/dist/{chunk-2ZDLQAO4.js → chunk-OMLNCNSZ.js} +60 -27
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/README.md +1 -1
- package/dist/plugin/skills/do/e2e-acceptance.md +28 -17
- package/dist/plugin/skills/do/impact-analysis.md +9 -1
- package/dist/plugin/skills/do/open-prs.md +23 -1
- package/dist/plugin/skills/do/pre-flight.md +104 -0
- package/dist/plugin/skills/do/requirements.md +11 -1
- package/dist/plugin/skills/do/unit-tests.md +9 -1
- package/dist/plugin/skills/do/validate-code.md +9 -1
- package/dist/plugin/skills/muggle-do/SKILL.md +66 -22
- package/dist/plugin/skills/muggle-test/SKILL.md +13 -1
- package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +13 -2
- package/dist/plugin/skills/muggle-test-regenerate-missing/SKILL.md +14 -21
- package/dist/plugin/skills/muggle-upgrade/SKILL.md +1 -1
- package/dist/release-manifest.json +4 -4
- package/dist/{src-2IDMKEJ5.js → src-ZRUONWKV.js} +1 -1
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/README.md +1 -1
- package/plugin/skills/do/e2e-acceptance.md +28 -17
- package/plugin/skills/do/impact-analysis.md +9 -1
- package/plugin/skills/do/open-prs.md +23 -1
- package/plugin/skills/do/pre-flight.md +104 -0
- package/plugin/skills/do/requirements.md +11 -1
- package/plugin/skills/do/unit-tests.md +9 -1
- package/plugin/skills/do/validate-code.md +9 -1
- package/plugin/skills/muggle-do/SKILL.md +66 -22
- package/plugin/skills/muggle-test/SKILL.md +13 -1
- package/plugin/skills/muggle-test-feature-local/SKILL.md +13 -2
- package/plugin/skills/muggle-test-regenerate-missing/SKILL.md +14 -21
- package/plugin/skills/muggle-upgrade/SKILL.md +1 -1
- package/scripts/postinstall.mjs +45 -0
package/README.md
CHANGED
|
@@ -55,7 +55,9 @@ This installs:
|
|
|
55
55
|
npm install -g @muggleai/works
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -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;
|
|
@@ -2505,7 +2509,8 @@ async function executeReplay(params) {
|
|
|
2505
2509
|
scriptFilePath: inputFilePath,
|
|
2506
2510
|
authFilePath,
|
|
2507
2511
|
timeoutMs,
|
|
2508
|
-
showUi: params.showUi
|
|
2512
|
+
showUi: params.showUi,
|
|
2513
|
+
freshSession: params.freshSession
|
|
2509
2514
|
});
|
|
2510
2515
|
const completedAt = Date.now();
|
|
2511
2516
|
const executionTimeMs = completedAt - startedAt;
|
|
@@ -3239,6 +3244,12 @@ var WorkflowStartTestScriptGenerationInputSchema = z.object({
|
|
|
3239
3244
|
expectedResult: z.string().min(1).describe("Expected result"),
|
|
3240
3245
|
workflowParams: WorkflowParamsSchema
|
|
3241
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
|
+
});
|
|
3242
3253
|
var WorkflowGetLatestScriptGenByTestCaseInputSchema = z.object({
|
|
3243
3254
|
testCaseId: IdSchema.describe("Test case ID (UUID)")
|
|
3244
3255
|
});
|
|
@@ -4208,6 +4219,25 @@ var workflowTools = [
|
|
|
4208
4219
|
};
|
|
4209
4220
|
}
|
|
4210
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
|
+
},
|
|
4211
4241
|
{
|
|
4212
4242
|
name: "muggle-remote-wf-get-ts-gen-latest-run",
|
|
4213
4243
|
description: "Get the latest run status for a test script generation workflow runtime.",
|
|
@@ -5146,14 +5176,6 @@ __export(local_exports, {
|
|
|
5146
5176
|
executeTool: () => executeTool,
|
|
5147
5177
|
getTool: () => getTool
|
|
5148
5178
|
});
|
|
5149
|
-
var AuthLoginInputSchema2 = z.object({
|
|
5150
|
-
waitForCompletion: z.boolean().optional().describe("Whether to wait for browser login completion before returning. Default: true"),
|
|
5151
|
-
timeoutMs: z.number().int().positive().min(1e3).max(9e5).optional().describe("Maximum time to wait for login completion in milliseconds. Default: 120000")
|
|
5152
|
-
});
|
|
5153
|
-
var AuthPollInputSchema2 = z.object({
|
|
5154
|
-
deviceCode: z.string().optional().describe("Device code from the login response. Optional if a login was recently started.")
|
|
5155
|
-
});
|
|
5156
|
-
var EmptyInputSchema2 = z.object({});
|
|
5157
5179
|
var TestCaseDetailsSchema = z.object({
|
|
5158
5180
|
/** Cloud test case ID. */
|
|
5159
5181
|
id: MuggleEntityIdSchema.describe("Cloud test case ID (UUID)"),
|
|
@@ -5200,7 +5222,9 @@ var ExecuteTestGenerationInputSchema = z.object({
|
|
|
5200
5222
|
/** Optional timeout. */
|
|
5201
5223
|
timeoutMs: z.number().int().positive().optional().describe("Timeout in milliseconds (default: 300000 = 5 min)"),
|
|
5202
5224
|
/** Show the electron-app UI during execution. Default: visible window. Pass false to run headless. */
|
|
5203
|
-
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.")
|
|
5204
5228
|
});
|
|
5205
5229
|
var ExecuteReplayInputSchema = z.object({
|
|
5206
5230
|
/** Test script metadata from muggle-remote-test-script-get. */
|
|
@@ -5212,7 +5236,9 @@ var ExecuteReplayInputSchema = z.object({
|
|
|
5212
5236
|
/** Optional timeout. */
|
|
5213
5237
|
timeoutMs: z.number().int().positive().optional().describe("Timeout in milliseconds (default: 180000 = 3 min)"),
|
|
5214
5238
|
/** Show the electron-app UI during execution. Default: visible window. Pass false to run headless. */
|
|
5215
|
-
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.")
|
|
5216
5242
|
});
|
|
5217
5243
|
var CancelExecutionInputSchema = z.object({
|
|
5218
5244
|
runId: MuggleEntityIdSchema.describe("Run ID (UUID) to cancel")
|
|
@@ -5240,6 +5266,7 @@ var ListSessionsInputSchema = z.object({
|
|
|
5240
5266
|
var CleanupSessionsInputSchema = z.object({
|
|
5241
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.")
|
|
5242
5268
|
});
|
|
5269
|
+
var EmptyInputSchema2 = z.object({});
|
|
5243
5270
|
|
|
5244
5271
|
// packages/mcps/src/mcp/tools/local/tool-registry.ts
|
|
5245
5272
|
function createChildLogger2(correlationId) {
|
|
@@ -5464,7 +5491,8 @@ var executeTestGenerationTool = {
|
|
|
5464
5491
|
testCase: input.testCase,
|
|
5465
5492
|
localUrl: input.localUrl,
|
|
5466
5493
|
timeoutMs: input.timeoutMs,
|
|
5467
|
-
showUi
|
|
5494
|
+
showUi,
|
|
5495
|
+
freshSession: input.freshSession
|
|
5468
5496
|
});
|
|
5469
5497
|
const content = [
|
|
5470
5498
|
"## Test Generation " + (result.status === "passed" ? "Successful" : "Failed"),
|
|
@@ -5503,7 +5531,8 @@ var executeReplayTool = {
|
|
|
5503
5531
|
actionScript: input.actionScript,
|
|
5504
5532
|
localUrl: input.localUrl,
|
|
5505
5533
|
timeoutMs: input.timeoutMs,
|
|
5506
|
-
showUi
|
|
5534
|
+
showUi,
|
|
5535
|
+
freshSession: input.freshSession
|
|
5507
5536
|
});
|
|
5508
5537
|
const content = [
|
|
5509
5538
|
"## Test Replay " + (result.status === "passed" ? "Successful" : "Failed"),
|
|
@@ -5799,6 +5828,7 @@ __export(e2e_exports2, {
|
|
|
5799
5828
|
WorkflowMemoryParamsSchema: () => WorkflowMemoryParamsSchema,
|
|
5800
5829
|
WorkflowParamsSchema: () => WorkflowParamsSchema,
|
|
5801
5830
|
WorkflowStartTestCaseDetectionInputSchema: () => WorkflowStartTestCaseDetectionInputSchema,
|
|
5831
|
+
WorkflowStartTestScriptGenerationBulkInputSchema: () => WorkflowStartTestScriptGenerationBulkInputSchema,
|
|
5802
5832
|
WorkflowStartTestScriptGenerationInputSchema: () => WorkflowStartTestScriptGenerationInputSchema,
|
|
5803
5833
|
WorkflowStartTestScriptReplayBulkInputSchema: () => WorkflowStartTestScriptReplayBulkInputSchema,
|
|
5804
5834
|
WorkflowStartTestScriptReplayInputSchema: () => WorkflowStartTestScriptReplayInputSchema,
|
|
@@ -5824,8 +5854,6 @@ function getQaTools() {
|
|
|
5824
5854
|
// packages/mcps/src/mcp/local/index.ts
|
|
5825
5855
|
var local_exports2 = {};
|
|
5826
5856
|
__export(local_exports2, {
|
|
5827
|
-
AuthLoginInputSchema: () => AuthLoginInputSchema2,
|
|
5828
|
-
AuthPollInputSchema: () => AuthPollInputSchema2,
|
|
5829
5857
|
AuthService: () => AuthService,
|
|
5830
5858
|
CancelExecutionInputSchema: () => CancelExecutionInputSchema,
|
|
5831
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-
|
|
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
|
|
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,
|
|
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
|
|
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-
|
|
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
|
|
810
|
+
return path4.join(getDataDir(), ELECTRON_APP_DIR);
|
|
811
811
|
}
|
|
812
812
|
function getCursorSkillsDir() {
|
|
813
|
-
return
|
|
813
|
+
return path4.join(homedir(), CURSOR_SKILLS_DIR, CURSOR_SKILLS_SUBDIR);
|
|
814
814
|
}
|
|
815
815
|
function getInstallManifestPath() {
|
|
816
|
-
return
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
1109
|
+
return path4.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
1110
1110
|
case "win32":
|
|
1111
|
-
return
|
|
1111
|
+
return path4.join(versionDir, "MuggleAI.exe");
|
|
1112
1112
|
case "linux":
|
|
1113
|
-
return
|
|
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 =
|
|
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: "
|
|
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
|
|
1643
|
+
return path4.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
1644
1644
|
case "win32":
|
|
1645
|
-
return
|
|
1645
|
+
return path4.join(versionDir, "MuggleAI.exe");
|
|
1646
1646
|
case "linux":
|
|
1647
|
-
return
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
1963
|
+
return path4.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
1931
1964
|
case "win32":
|
|
1932
|
-
return
|
|
1965
|
+
return path4.join(versionDir, "MuggleAI.exe");
|
|
1933
1966
|
case "linux":
|
|
1934
|
-
return
|
|
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 =
|
|
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 =
|
|
2121
|
+
const metadataPath = path4.join(versionDir, INSTALL_METADATA_FILE_NAME2);
|
|
2089
2122
|
writeInstallMetadata2({
|
|
2090
2123
|
metadataPath,
|
|
2091
2124
|
version,
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-
|
|
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-
|
|
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.8.
|
|
4
|
+
"version": "4.8.2",
|
|
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.8.
|
|
5
|
+
"version": "4.8.2",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/dist/plugin/README.md
CHANGED
|
@@ -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
|
|
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
|
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
# E2E / acceptance agent
|
|
1
|
+
# E2E / acceptance agent (Stage 6/7)
|
|
2
2
|
|
|
3
3
|
You are running **end-to-end (E2E) acceptance** test cases against code changes using Muggle AI's local testing infrastructure. These tests simulate real users in a browser — they are not unit tests.
|
|
4
4
|
|
|
5
|
+
## Turn preamble
|
|
6
|
+
|
|
7
|
+
Start the turn with:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
**Stage 6/7 — E2E acceptance** — running browser tests against the validation target from pre-flight.
|
|
11
|
+
```
|
|
12
|
+
|
|
5
13
|
## Design
|
|
6
14
|
|
|
7
15
|
E2E acceptance testing runs **locally** using the `test-feature-local` approach:
|
|
@@ -15,32 +23,35 @@ This guarantees E2E acceptance tests always run — no dependency on cloud repla
|
|
|
15
23
|
|
|
16
24
|
## Input
|
|
17
25
|
|
|
18
|
-
You receive:
|
|
19
|
-
|
|
26
|
+
You receive everything from `state.md` already — pre-flight resolved it:
|
|
27
|
+
|
|
28
|
+
- `localUrl` — the locally running dev server URL
|
|
29
|
+
- `projectId` — the chosen Muggle project
|
|
30
|
+
- The validation strategy (`local-e2e`, `staging-replay`, `unit-only`, `skip`)
|
|
31
|
+
- Test-user credential status (existing / new / skip)
|
|
20
32
|
- The list of changed repos, files, and a summary of changes
|
|
21
33
|
- The requirements goal
|
|
22
|
-
- `localUrl` per repo (from `muggle-repos.json`) — the locally running dev server URL
|
|
23
34
|
|
|
24
35
|
## Your Job
|
|
25
36
|
|
|
26
|
-
### Step 0:
|
|
37
|
+
### Step 0: Consume pre-flight (no user questions)
|
|
38
|
+
|
|
39
|
+
Read `state.md`. If the validation strategy is `unit-only` or `skip`, **do not run this stage** — skip to stage 7 and record the skip reason. Otherwise use `localUrl` directly; **do not ask the user** for it.
|
|
40
|
+
|
|
41
|
+
If `localUrl` or `projectId` is missing from `state.md`, that is a pre-flight bug. **Do not paper over it by asking the user** — escalate once with the session path and halt. The fix is to expand `pre-flight.md`, not to grow a new question here.
|
|
27
42
|
|
|
28
|
-
|
|
29
|
-
> "E2E acceptance testing requires a running local server. What URL is the `<repo>` app running on? (e.g. `http://localhost:3000`)"
|
|
43
|
+
### Step 0.5: Pre-flight verification probes
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
Before launching Electron, run these live checks and fail loudly if any fails:
|
|
32
46
|
|
|
33
|
-
|
|
47
|
+
1. `curl -s -o /dev/null -w "%{http_code}" <localUrl>` — expect 2xx or 3xx. If the dev server isn't up, halt with the exact command the user needs to start it.
|
|
48
|
+
2. If a backend URL is recorded, probe its health endpoint. A 5xx or unreachable backend means the dashboard will render in an error state and test results will be meaningless — halt.
|
|
49
|
+
3. `muggle-remote-auth-status` — must be `authenticated`. If not, the pre-flight missed this; escalate.
|
|
50
|
+
4. If test credentials were marked `existing`, confirm the Auth0 tenant in the repo's env matches the tenant the secrets were created under (recorded in `state.md`). Tenant mismatch → halt with "existing secrets target tenant X, local dev targets tenant Y — update pre-flight to collect new credentials."
|
|
34
51
|
|
|
35
|
-
|
|
36
|
-
- If **authenticated**: print the logged-in email and ask via `AskQuestion`:
|
|
37
|
-
> "You're logged in as **{email}**. Continue with this account?"
|
|
38
|
-
- Option 1: "Yes, continue"
|
|
39
|
-
- Option 2: "No, switch account"
|
|
40
|
-
If the user picks "switch account", call `muggle-remote-auth-login` with `forceNewSession: true` then `muggle-remote-auth-poll`.
|
|
41
|
-
- If **not signed in or expired**: `muggle-remote-auth-login` then `muggle-remote-auth-poll`
|
|
52
|
+
### Step 1: Authentication already verified
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
Pre-flight handled auth. If `muggle-remote-auth-status` somehow shows expired here (session clock skew, etc.), re-auth silently via `muggle-remote-auth-login` + `muggle-remote-auth-poll` — but do not ask the user "continue with this account?" again.
|
|
44
55
|
|
|
45
56
|
### Step 2: Get Test Cases
|
|
46
57
|
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
# Impact Analysis Agent
|
|
1
|
+
# Impact Analysis Agent (Stage 3/7)
|
|
2
2
|
|
|
3
3
|
You are analyzing git repositories to determine which ones have actual code changes that need to go through the dev cycle pipeline.
|
|
4
4
|
|
|
5
|
+
## Turn preamble
|
|
6
|
+
|
|
7
|
+
Start the turn with:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
**Stage 3/7 — Impact analysis** — diffing each affected repo against its default branch.
|
|
11
|
+
```
|
|
12
|
+
|
|
5
13
|
## Input
|
|
6
14
|
|
|
7
15
|
You receive:
|
|
@@ -1,7 +1,29 @@
|
|
|
1
|
-
# PR Creation Agent
|
|
1
|
+
# PR Creation Agent (Stage 7/7)
|
|
2
2
|
|
|
3
3
|
You are creating pull requests for each repository that has changes after a successful dev cycle run.
|
|
4
4
|
|
|
5
|
+
## Turn preamble
|
|
6
|
+
|
|
7
|
+
Start the turn with:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
**Stage 7/7 — Open PR** — rendering the visual walkthrough and pushing the PR.
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Non-negotiable: visual walkthrough is required
|
|
14
|
+
|
|
15
|
+
**You MUST invoke `muggle-pr-visual-walkthrough` (Mode B) to render the E2E section of the PR body.** Hand-writing the PR body with a text summary and `gh pr create` is a stage failure — reviewers rely on the dashboard links and per-step screenshots the walkthrough produces.
|
|
16
|
+
|
|
17
|
+
If the E2E stage was skipped (validation was `unit-only` or `skip`), you may omit the walkthrough section — but mark the PR title with `[UNVERIFIED]` or `[UNIT-ONLY]` accordingly, and record the reason in the PR body under `## Validation`.
|
|
18
|
+
|
|
19
|
+
Before calling `gh pr create`, self-check:
|
|
20
|
+
|
|
21
|
+
- [ ] `muggle-pr-visual-walkthrough` was invoked (or the skip reason is recorded).
|
|
22
|
+
- [ ] The `body` returned by the skill is embedded verbatim in the PR body.
|
|
23
|
+
- [ ] If `comment` is non-null, it will be posted as a follow-up after the PR is created.
|
|
24
|
+
|
|
25
|
+
If you cannot check all three, **halt** — do not create the PR. Fix the upstream stage first.
|
|
26
|
+
|
|
5
27
|
## Input
|
|
6
28
|
|
|
7
29
|
You receive:
|