@muggleai/works 4.4.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -13
- package/dist/{chunk-PMI2DI3V.js → chunk-TP4T4T2Z.js} +348 -105
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/README.md +1 -0
- package/dist/plugin/skills/do/e2e-acceptance.md +6 -3
- package/dist/plugin/skills/do/open-prs.md +35 -74
- package/dist/plugin/skills/muggle-pr-visual-walkthrough/SKILL.md +181 -0
- package/dist/plugin/skills/muggle-test/SKILL.md +146 -121
- package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +66 -16
- package/dist/plugin/skills/muggle-test-import/SKILL.md +127 -25
- package/dist/plugin/skills/muggle-test-regenerate-missing/SKILL.md +201 -0
- package/dist/plugin/skills/muggle-test-regenerate-missing/evals/evals.json +58 -0
- package/dist/plugin/skills/muggle-test-regenerate-missing/evals/trigger-eval.json +22 -0
- package/dist/release-manifest.json +7 -0
- package/package.json +7 -7
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/README.md +1 -0
- package/plugin/skills/do/e2e-acceptance.md +6 -3
- package/plugin/skills/do/open-prs.md +35 -74
- package/plugin/skills/muggle-pr-visual-walkthrough/SKILL.md +181 -0
- package/plugin/skills/muggle-test/SKILL.md +146 -121
- package/plugin/skills/muggle-test-feature-local/SKILL.md +66 -16
- package/plugin/skills/muggle-test-import/SKILL.md +127 -25
- package/plugin/skills/muggle-test-regenerate-missing/SKILL.md +201 -0
- package/plugin/skills/muggle-test-regenerate-missing/evals/evals.json +58 -0
- package/plugin/skills/muggle-test-regenerate-missing/evals/trigger-eval.json +22 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as fs3 from 'fs';
|
|
2
|
-
import { readFileSync, existsSync,
|
|
2
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync, rmSync, readdirSync, createWriteStream, statSync } from 'fs';
|
|
3
3
|
import * as os3 from 'os';
|
|
4
4
|
import { platform, arch, homedir } from 'os';
|
|
5
5
|
import * as path2 from 'path';
|
|
6
|
-
import { dirname, resolve
|
|
6
|
+
import { join, dirname, resolve } from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import winston from 'winston';
|
|
9
9
|
import axios, { AxiosError } from 'axios';
|
|
@@ -34,6 +34,49 @@ var DATA_DIR_NAME = ".muggle-ai";
|
|
|
34
34
|
function getDataDir() {
|
|
35
35
|
return path2.join(os3.homedir(), DATA_DIR_NAME);
|
|
36
36
|
}
|
|
37
|
+
var DEV_MANIFEST = {
|
|
38
|
+
release: "dev",
|
|
39
|
+
buildId: "dev",
|
|
40
|
+
commitSha: "dev",
|
|
41
|
+
buildTime: "1970-01-01T00:00:00Z",
|
|
42
|
+
serviceName: "muggle-ai-works-mcp"
|
|
43
|
+
};
|
|
44
|
+
var MANIFEST_PATH = (() => {
|
|
45
|
+
try {
|
|
46
|
+
return join(dirname(fileURLToPath(import.meta.url)), "release-manifest.json");
|
|
47
|
+
} catch {
|
|
48
|
+
return "release-manifest.json";
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
var cachedManifest;
|
|
52
|
+
function isValidManifest(value) {
|
|
53
|
+
if (typeof value !== "object" || value === null) return false;
|
|
54
|
+
const m = value;
|
|
55
|
+
return typeof m.release === "string" && typeof m.buildId === "string" && typeof m.commitSha === "string" && typeof m.buildTime === "string" && typeof m.serviceName === "string";
|
|
56
|
+
}
|
|
57
|
+
function readReleaseManifest() {
|
|
58
|
+
if (cachedManifest !== void 0) return cachedManifest;
|
|
59
|
+
try {
|
|
60
|
+
const raw = readFileSync(MANIFEST_PATH, "utf8");
|
|
61
|
+
const parsed = JSON.parse(raw);
|
|
62
|
+
if (!isValidManifest(parsed)) {
|
|
63
|
+
cachedManifest = DEV_MANIFEST;
|
|
64
|
+
return cachedManifest;
|
|
65
|
+
}
|
|
66
|
+
cachedManifest = parsed;
|
|
67
|
+
return cachedManifest;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const code = err.code;
|
|
70
|
+
if (code !== "ENOENT") {
|
|
71
|
+
console.warn(
|
|
72
|
+
"release_manifest: unexpected error reading manifest, using DEV fallback",
|
|
73
|
+
{ error: err.message }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
cachedManifest = DEV_MANIFEST;
|
|
77
|
+
return cachedManifest;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
37
80
|
|
|
38
81
|
// packages/mcps/src/shared/config.ts
|
|
39
82
|
var DEFAULT_PROMPT_SERVICE_PRODUCTION_URL = "https://promptservice.muggle-ai.com";
|
|
@@ -303,9 +346,10 @@ function getConfig() {
|
|
|
303
346
|
if (configInstance) {
|
|
304
347
|
return configInstance;
|
|
305
348
|
}
|
|
349
|
+
const manifest = readReleaseManifest();
|
|
306
350
|
configInstance = {
|
|
307
351
|
serverName: "muggle",
|
|
308
|
-
serverVersion:
|
|
352
|
+
serverVersion: manifest.release,
|
|
309
353
|
logLevel: process.env.LOG_LEVEL ?? "info",
|
|
310
354
|
auth0: buildAuth0Config(),
|
|
311
355
|
e2e: buildE2eConfig(),
|
|
@@ -425,6 +469,14 @@ __export(e2e_exports2, {
|
|
|
425
469
|
ApiKeyRevokeInputSchema: () => ApiKeyRevokeInputSchema,
|
|
426
470
|
AuthLoginInputSchema: () => AuthLoginInputSchema,
|
|
427
471
|
AuthPollInputSchema: () => AuthPollInputSchema,
|
|
472
|
+
BulkPreviewJobCancelInputSchema: () => BulkPreviewJobCancelInputSchema,
|
|
473
|
+
BulkPreviewJobGetInputSchema: () => BulkPreviewJobGetInputSchema,
|
|
474
|
+
BulkPreviewJobKindSchema: () => BulkPreviewJobKindSchema,
|
|
475
|
+
BulkPreviewJobListInputSchema: () => BulkPreviewJobListInputSchema,
|
|
476
|
+
BulkPreviewJobStatusSchema: () => BulkPreviewJobStatusSchema,
|
|
477
|
+
BulkPreviewPromptSchema: () => BulkPreviewPromptSchema,
|
|
478
|
+
BulkPreviewSubmitTestCaseInputSchema: () => BulkPreviewSubmitTestCaseInputSchema,
|
|
479
|
+
BulkPreviewSubmitUseCaseInputSchema: () => BulkPreviewSubmitUseCaseInputSchema,
|
|
428
480
|
EmptyInputSchema: () => EmptyInputSchema,
|
|
429
481
|
GatewayError: () => GatewayError,
|
|
430
482
|
IdSchema: () => IdSchema,
|
|
@@ -467,11 +519,11 @@ __export(e2e_exports2, {
|
|
|
467
519
|
TestCaseListInputSchema: () => TestCaseListInputSchema,
|
|
468
520
|
TestScriptGetInputSchema: () => TestScriptGetInputSchema,
|
|
469
521
|
TestScriptListInputSchema: () => TestScriptListInputSchema,
|
|
470
|
-
TestScriptListPaginatedInputSchema: () => TestScriptListPaginatedInputSchema,
|
|
471
522
|
TokenPackageIdSchema: () => TokenPackageIdSchema,
|
|
472
523
|
TokenUsageFilterTypeSchema: () => TokenUsageFilterTypeSchema,
|
|
473
524
|
UseCaseCandidatesApproveInputSchema: () => UseCaseCandidatesApproveInputSchema,
|
|
474
525
|
UseCaseCreateFromPromptsInputSchema: () => UseCaseCreateFromPromptsInputSchema,
|
|
526
|
+
UseCaseCreateInputSchema: () => UseCaseCreateInputSchema,
|
|
475
527
|
UseCaseDiscoveryMemoryGetInputSchema: () => UseCaseDiscoveryMemoryGetInputSchema,
|
|
476
528
|
UseCaseGetInputSchema: () => UseCaseGetInputSchema,
|
|
477
529
|
UseCaseListInputSchema: () => UseCaseListInputSchema,
|
|
@@ -671,8 +723,9 @@ var AuthService = class {
|
|
|
671
723
|
}
|
|
672
724
|
/**
|
|
673
725
|
* Start the device code flow.
|
|
726
|
+
* @param options.forceNewSession - Clear existing Auth0 browser session before login to allow account switching.
|
|
674
727
|
*/
|
|
675
|
-
async startDeviceCodeFlow() {
|
|
728
|
+
async startDeviceCodeFlow(options) {
|
|
676
729
|
const logger14 = getLogger();
|
|
677
730
|
const config = getConfig();
|
|
678
731
|
const { domain, clientId, audience, scopes } = config.localQa.auth0;
|
|
@@ -709,15 +762,25 @@ var AuthService = class {
|
|
|
709
762
|
userCode: data.user_code,
|
|
710
763
|
expiresAt: new Date(Date.now() + data.expires_in * 1e3).toISOString()
|
|
711
764
|
});
|
|
765
|
+
let browserUrl = data.verification_uri_complete;
|
|
766
|
+
if (options?.forceNewSession) {
|
|
767
|
+
const logoutUrl = new URL(`https://${domain}/v2/logout`);
|
|
768
|
+
logoutUrl.searchParams.set("client_id", clientId);
|
|
769
|
+
logoutUrl.searchParams.set("returnTo", data.verification_uri_complete);
|
|
770
|
+
browserUrl = logoutUrl.toString();
|
|
771
|
+
logger14.info("Force new session: opening logout-redirect URL", {
|
|
772
|
+
logoutUrl: browserUrl
|
|
773
|
+
});
|
|
774
|
+
}
|
|
712
775
|
const browserOpenResult = await openBrowserUrl({
|
|
713
|
-
url:
|
|
776
|
+
url: browserUrl
|
|
714
777
|
});
|
|
715
778
|
if (browserOpenResult.opened) {
|
|
716
779
|
logger14.info("Browser opened for device code login");
|
|
717
780
|
} else {
|
|
718
781
|
logger14.warn("Failed to open browser for device code login", {
|
|
719
782
|
error: browserOpenResult.error,
|
|
720
|
-
|
|
783
|
+
url: browserUrl
|
|
721
784
|
});
|
|
722
785
|
}
|
|
723
786
|
return {
|
|
@@ -2504,7 +2567,7 @@ var getCredentialsFilePath = getApiKeyFilePath;
|
|
|
2504
2567
|
|
|
2505
2568
|
// packages/mcps/src/shared/auth.ts
|
|
2506
2569
|
var logger4 = getLogger();
|
|
2507
|
-
async function startDeviceCodeFlow(config) {
|
|
2570
|
+
async function startDeviceCodeFlow(config, options) {
|
|
2508
2571
|
const deviceCodeUrl = `https://${config.domain}/oauth/device/code`;
|
|
2509
2572
|
try {
|
|
2510
2573
|
logger4.info("[Auth] Starting device code flow", {
|
|
@@ -2529,8 +2592,16 @@ async function startDeviceCodeFlow(config) {
|
|
|
2529
2592
|
userCode: data.user_code,
|
|
2530
2593
|
expiresIn: data.expires_in
|
|
2531
2594
|
});
|
|
2595
|
+
let browserUrl = data.verification_uri_complete;
|
|
2596
|
+
if (options?.forceNewSession) {
|
|
2597
|
+
const logoutUrl = new URL(`https://${config.domain}/v2/logout`);
|
|
2598
|
+
logoutUrl.searchParams.set("client_id", config.clientId);
|
|
2599
|
+
logoutUrl.searchParams.set("returnTo", data.verification_uri_complete);
|
|
2600
|
+
browserUrl = logoutUrl.toString();
|
|
2601
|
+
logger4.info("[Auth] Force new session: opening logout-redirect URL");
|
|
2602
|
+
}
|
|
2532
2603
|
const browserOpenResult = await openBrowserUrl({
|
|
2533
|
-
url:
|
|
2604
|
+
url: browserUrl
|
|
2534
2605
|
});
|
|
2535
2606
|
if (browserOpenResult.opened) {
|
|
2536
2607
|
logger4.info("[Auth] Browser opened for device code login");
|
|
@@ -2854,8 +2925,10 @@ var LocalRunUploadInputSchema = z.object({
|
|
|
2854
2925
|
|
|
2855
2926
|
// packages/mcps/src/mcp/e2e/contracts/index.ts
|
|
2856
2927
|
var PaginationInputSchema = z.object({
|
|
2857
|
-
page: z.number().int().positive().
|
|
2858
|
-
pageSize: z.number().int().positive().max(100).
|
|
2928
|
+
page: z.number().int().positive().default(1).describe("Page number, 1-based. Defaults to 1 (the first page)."),
|
|
2929
|
+
pageSize: z.number().int().positive().max(100).default(10).describe("Number of items per page. Defaults to 10, max 100."),
|
|
2930
|
+
sortBy: z.enum(["createdAt", "updatedAt"]).default("createdAt").describe("Field to sort by. Defaults to createdAt (stable under concurrent writes)."),
|
|
2931
|
+
sortOrder: z.enum(["asc", "desc"]).default("desc").describe("Sort direction. Defaults to desc (newest first).")
|
|
2859
2932
|
});
|
|
2860
2933
|
var IdSchema = MuggleEntityIdSchema;
|
|
2861
2934
|
var RunBatchIdSchema = MuggleEntityIdSchema.describe("Bulk replay run batch ID (UUID)");
|
|
@@ -2961,15 +3034,67 @@ var UseCasePromptPreviewInputSchema = z.object({
|
|
|
2961
3034
|
});
|
|
2962
3035
|
var UseCaseCreateFromPromptsInputSchema = z.object({
|
|
2963
3036
|
projectId: IdSchema.describe("Project ID (UUID) to create use cases for"),
|
|
2964
|
-
|
|
2965
|
-
instruction: z.string().min(1).describe("Natural language instruction describing the use case")
|
|
2966
|
-
})).min(1).describe("Array of prompts to generate use cases from")
|
|
3037
|
+
instructions: z.array(z.string().min(1)).min(1).describe('Natural-language instructions \u2014 one use case is generated per string (e.g., ["As a user, I can log in"])')
|
|
2967
3038
|
});
|
|
2968
3039
|
var UseCaseUpdateFromPromptInputSchema = z.object({
|
|
2969
3040
|
projectId: IdSchema.describe("Project ID (UUID)"),
|
|
2970
3041
|
useCaseId: IdSchema.describe("Use case ID (UUID) to update"),
|
|
2971
3042
|
instruction: z.string().min(1).describe("Natural language instruction to regenerate the use case from")
|
|
2972
3043
|
});
|
|
3044
|
+
var UseCaseCreateInputSchema = z.object({
|
|
3045
|
+
projectId: IdSchema.describe("Project ID (UUID) the use case belongs to"),
|
|
3046
|
+
title: z.string().min(1).describe("Use case title"),
|
|
3047
|
+
description: z.string().min(1).describe("Description of the use case, including actor and preconditions"),
|
|
3048
|
+
userStory: z.string().min(1).describe("One-line user story from the end-user point of view"),
|
|
3049
|
+
url: z.string().url().optional().describe("URL where the use case takes place (defaults to project URL)"),
|
|
3050
|
+
useCaseBreakdown: z.array(z.object({
|
|
3051
|
+
requirement: z.string().min(1).describe("One requirement of the use case"),
|
|
3052
|
+
acceptanceCriteria: z.string().min(1).describe("Concrete, measurable acceptance criteria for the requirement")
|
|
3053
|
+
})).describe("Main/alternative/error flows broken down as requirement + acceptance criteria pairs"),
|
|
3054
|
+
status: z.enum(["DRAFT", "IN_REVIEW", "APPROVED", "IMPLEMENTED", "ARCHIVED"]).describe("Use case status"),
|
|
3055
|
+
priority: z.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]).describe("Use case priority"),
|
|
3056
|
+
source: z.enum(["PRD_FILE", "SITEMAP", "CRAWLER", "PROMPT", "MANUAL"]).describe("How this use case was produced"),
|
|
3057
|
+
category: z.string().optional().describe("Optional category")
|
|
3058
|
+
});
|
|
3059
|
+
var BulkPreviewPromptSchema = z.object({
|
|
3060
|
+
clientRef: z.string().max(128).optional().describe("Optional caller-supplied reference echoed back on results"),
|
|
3061
|
+
instruction: z.string().min(1).max(4e3).describe("Natural language instruction (max 4000 chars)")
|
|
3062
|
+
});
|
|
3063
|
+
var BulkPreviewSubmitUseCaseInputSchema = z.object({
|
|
3064
|
+
projectId: IdSchema.describe("Project ID (UUID) the use cases belong to"),
|
|
3065
|
+
prompts: z.array(BulkPreviewPromptSchema).min(1).max(100).describe("Prompts to generate use cases from (max 100 per request)")
|
|
3066
|
+
});
|
|
3067
|
+
var BulkPreviewSubmitTestCaseInputSchema = z.object({
|
|
3068
|
+
projectId: IdSchema.describe("Project ID (UUID)"),
|
|
3069
|
+
useCaseId: IdSchema.describe("Parent use case ID (UUID) the test cases will belong to"),
|
|
3070
|
+
prompts: z.array(BulkPreviewPromptSchema).min(1).max(100).describe("Prompts to generate test cases from (max 100 per request)")
|
|
3071
|
+
});
|
|
3072
|
+
var BulkPreviewJobStatusSchema = z.enum([
|
|
3073
|
+
"queued",
|
|
3074
|
+
"submitted",
|
|
3075
|
+
"running",
|
|
3076
|
+
"succeeded",
|
|
3077
|
+
"partial",
|
|
3078
|
+
"failed",
|
|
3079
|
+
"cancelled",
|
|
3080
|
+
"expired"
|
|
3081
|
+
]);
|
|
3082
|
+
var BulkPreviewJobKindSchema = z.enum(["useCase", "testCase"]);
|
|
3083
|
+
var BulkPreviewJobGetInputSchema = z.object({
|
|
3084
|
+
projectId: IdSchema.describe("Project ID (UUID)"),
|
|
3085
|
+
jobId: IdSchema.describe("Bulk-preview job ID (UUID)")
|
|
3086
|
+
});
|
|
3087
|
+
var BulkPreviewJobCancelInputSchema = z.object({
|
|
3088
|
+
projectId: IdSchema.describe("Project ID (UUID)"),
|
|
3089
|
+
jobId: IdSchema.describe("Bulk-preview job ID (UUID) to cancel")
|
|
3090
|
+
});
|
|
3091
|
+
var BulkPreviewJobListInputSchema = z.object({
|
|
3092
|
+
projectId: IdSchema.describe("Project ID (UUID) to list bulk-preview jobs for"),
|
|
3093
|
+
status: z.array(BulkPreviewJobStatusSchema).optional().describe("Optional filter \u2014 only return jobs matching any of these statuses"),
|
|
3094
|
+
kind: BulkPreviewJobKindSchema.optional().describe("Optional filter \u2014 only return jobs of this kind"),
|
|
3095
|
+
limit: z.number().int().min(1).max(100).optional().describe("Max jobs to return (default 20, max 100)"),
|
|
3096
|
+
cursor: z.string().optional().describe("Pagination cursor returned by a previous call")
|
|
3097
|
+
});
|
|
2973
3098
|
var TestCaseListInputSchema = z.object({
|
|
2974
3099
|
projectId: IdSchema.describe("Project ID (UUID) to list test cases for")
|
|
2975
3100
|
}).merge(PaginationInputSchema);
|
|
@@ -3006,9 +3131,6 @@ var TestScriptListInputSchema = z.object({
|
|
|
3006
3131
|
var TestScriptGetInputSchema = z.object({
|
|
3007
3132
|
testScriptId: IdSchema.describe("Test script ID (UUID) to retrieve")
|
|
3008
3133
|
});
|
|
3009
|
-
var TestScriptListPaginatedInputSchema = z.object({
|
|
3010
|
-
projectId: IdSchema.describe("Project ID (UUID) to list test scripts for")
|
|
3011
|
-
}).merge(PaginationInputSchema);
|
|
3012
3134
|
var ActionScriptGetInputSchema = z.object({
|
|
3013
3135
|
actionScriptId: IdSchema.describe("Action script ID (UUID) to retrieve")
|
|
3014
3136
|
});
|
|
@@ -3148,7 +3270,8 @@ var ApiKeyRevokeInputSchema = z.object({
|
|
|
3148
3270
|
});
|
|
3149
3271
|
var AuthLoginInputSchema = z.object({
|
|
3150
3272
|
waitForCompletion: z.boolean().optional().describe("Whether to wait for browser login completion before returning. Default: true"),
|
|
3151
|
-
timeoutMs: z.number().int().positive().min(1e3).max(9e5).optional().describe("Maximum time to wait for login completion in milliseconds. Default: 120000")
|
|
3273
|
+
timeoutMs: z.number().int().positive().min(1e3).max(9e5).optional().describe("Maximum time to wait for login completion in milliseconds. Default: 120000"),
|
|
3274
|
+
forceNewSession: z.boolean().optional().describe("Force a fresh login by clearing any existing Auth0 browser session before redirecting to the device activation page. Use this to switch accounts. Default: false")
|
|
3152
3275
|
});
|
|
3153
3276
|
var AuthPollInputSchema = z.object({
|
|
3154
3277
|
deviceCode: z.string().optional().describe("Device code from the login response. Optional if a login was recently started.")
|
|
@@ -3180,6 +3303,61 @@ var GatewayError = class extends Error {
|
|
|
3180
3303
|
this.details = params.details;
|
|
3181
3304
|
}
|
|
3182
3305
|
};
|
|
3306
|
+
|
|
3307
|
+
// packages/mcps/src/shared/host_detection.ts
|
|
3308
|
+
function detectMcpHost(env = process.env) {
|
|
3309
|
+
if (env.CLAUDE_CODE || env.CLAUDECODE || env.CLAUDE_CODE_SSE_PORT) {
|
|
3310
|
+
return "claude-code";
|
|
3311
|
+
}
|
|
3312
|
+
if (env.CURSOR_TRACE_ID || env.CURSOR_MCP) {
|
|
3313
|
+
return "cursor";
|
|
3314
|
+
}
|
|
3315
|
+
if (env.CODEX || env.OPENAI_CODEX) {
|
|
3316
|
+
return "codex";
|
|
3317
|
+
}
|
|
3318
|
+
if (env.WINDSURF_MCP || env.WINDSURF || env.CODEIUM_API_KEY) {
|
|
3319
|
+
return "windsurf";
|
|
3320
|
+
}
|
|
3321
|
+
return "unknown";
|
|
3322
|
+
}
|
|
3323
|
+
var INSTALL_ID_FILENAME = "install-id.json";
|
|
3324
|
+
var cachedInstallId;
|
|
3325
|
+
function installIdFilePath() {
|
|
3326
|
+
return join(getDataDir(), INSTALL_ID_FILENAME);
|
|
3327
|
+
}
|
|
3328
|
+
function generateAndPersist(filePath) {
|
|
3329
|
+
const installId = randomUUID();
|
|
3330
|
+
const contents = { installId };
|
|
3331
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
3332
|
+
writeFileSync(filePath, JSON.stringify(contents, null, 2) + "\n", "utf8");
|
|
3333
|
+
return installId;
|
|
3334
|
+
}
|
|
3335
|
+
function isValid(value) {
|
|
3336
|
+
return typeof value === "object" && value !== null && typeof value.installId === "string" && value.installId.length > 0;
|
|
3337
|
+
}
|
|
3338
|
+
function getInstallId() {
|
|
3339
|
+
if (cachedInstallId !== void 0) return cachedInstallId;
|
|
3340
|
+
const filePath = installIdFilePath();
|
|
3341
|
+
if (!existsSync(filePath)) {
|
|
3342
|
+
cachedInstallId = generateAndPersist(filePath);
|
|
3343
|
+
return cachedInstallId;
|
|
3344
|
+
}
|
|
3345
|
+
try {
|
|
3346
|
+
const raw = readFileSync(filePath, "utf8");
|
|
3347
|
+
const parsed = JSON.parse(raw);
|
|
3348
|
+
if (isValid(parsed)) {
|
|
3349
|
+
cachedInstallId = parsed.installId;
|
|
3350
|
+
return cachedInstallId;
|
|
3351
|
+
}
|
|
3352
|
+
cachedInstallId = generateAndPersist(filePath);
|
|
3353
|
+
return cachedInstallId;
|
|
3354
|
+
} catch {
|
|
3355
|
+
cachedInstallId = generateAndPersist(filePath);
|
|
3356
|
+
return cachedInstallId;
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
// packages/mcps/src/mcp/e2e/upstream-client.ts
|
|
3183
3361
|
var ALLOWED_UPSTREAM_PREFIXES = [
|
|
3184
3362
|
"/v1/protected/muggle-test/",
|
|
3185
3363
|
"/v1/protected/wallet/",
|
|
@@ -3226,8 +3404,17 @@ var PromptServiceClient = class {
|
|
|
3226
3404
|
* @returns Headers object.
|
|
3227
3405
|
*/
|
|
3228
3406
|
buildHeaders(credentials, correlationId) {
|
|
3407
|
+
const manifest = readReleaseManifest();
|
|
3408
|
+
const installId = getInstallId();
|
|
3409
|
+
const host = detectMcpHost();
|
|
3229
3410
|
const headers = {
|
|
3230
|
-
"X-Correlation-Id": correlationId
|
|
3411
|
+
"X-Correlation-Id": correlationId,
|
|
3412
|
+
"X-Client-Service-Name": manifest.serviceName,
|
|
3413
|
+
"X-Client-Release": manifest.release,
|
|
3414
|
+
"X-Client-Build-Id": manifest.buildId,
|
|
3415
|
+
"X-Client-Commit-Sha": manifest.commitSha,
|
|
3416
|
+
"X-Client-Install-Id": installId,
|
|
3417
|
+
"X-Client-Host": host
|
|
3231
3418
|
};
|
|
3232
3419
|
if (credentials.bearerToken) {
|
|
3233
3420
|
headers["Authorization"] = credentials.bearerToken.startsWith("Bearer ") ? credentials.bearerToken : `Bearer ${credentials.bearerToken}`;
|
|
@@ -3476,14 +3663,19 @@ var projectTools = [
|
|
|
3476
3663
|
},
|
|
3477
3664
|
{
|
|
3478
3665
|
name: "muggle-remote-project-list",
|
|
3479
|
-
description: "List
|
|
3666
|
+
description: "List projects accessible to the authenticated user. Returns up to 10 items per page by default (max 100). Response includes pagination metadata (totalCount, totalPages, hasMore) \u2014 check `hasMore` to decide whether to fetch additional pages.",
|
|
3480
3667
|
inputSchema: ProjectListInputSchema,
|
|
3481
3668
|
mapToUpstream: (input) => {
|
|
3482
3669
|
const data = input;
|
|
3483
3670
|
return {
|
|
3484
3671
|
method: "GET",
|
|
3485
3672
|
path: `${MUGGLE_TEST_PREFIX}/projects`,
|
|
3486
|
-
queryParams: {
|
|
3673
|
+
queryParams: {
|
|
3674
|
+
page: data.page,
|
|
3675
|
+
pageSize: data.pageSize,
|
|
3676
|
+
sortBy: data.sortBy,
|
|
3677
|
+
sortOrder: data.sortOrder
|
|
3678
|
+
}
|
|
3487
3679
|
};
|
|
3488
3680
|
}
|
|
3489
3681
|
},
|
|
@@ -3528,14 +3720,20 @@ var useCaseTools = [
|
|
|
3528
3720
|
},
|
|
3529
3721
|
{
|
|
3530
3722
|
name: "muggle-remote-use-case-list",
|
|
3531
|
-
description: "List
|
|
3723
|
+
description: "List use cases for a project. Returns up to 10 items per page by default (max 100). Response includes pagination metadata (totalCount, totalPages, hasMore) \u2014 check `hasMore` to decide whether to fetch additional pages.",
|
|
3532
3724
|
inputSchema: UseCaseListInputSchema,
|
|
3533
3725
|
mapToUpstream: (input) => {
|
|
3534
3726
|
const data = input;
|
|
3535
3727
|
return {
|
|
3536
3728
|
method: "GET",
|
|
3537
3729
|
path: `${MUGGLE_TEST_PREFIX}/use-cases`,
|
|
3538
|
-
queryParams: {
|
|
3730
|
+
queryParams: {
|
|
3731
|
+
projectId: data.projectId,
|
|
3732
|
+
page: data.page,
|
|
3733
|
+
pageSize: data.pageSize,
|
|
3734
|
+
sortBy: data.sortBy,
|
|
3735
|
+
sortOrder: data.sortOrder
|
|
3736
|
+
}
|
|
3539
3737
|
};
|
|
3540
3738
|
}
|
|
3541
3739
|
},
|
|
@@ -3573,7 +3771,10 @@ var useCaseTools = [
|
|
|
3573
3771
|
return {
|
|
3574
3772
|
method: "POST",
|
|
3575
3773
|
path: `${MUGGLE_TEST_PREFIX}/projects/${data.projectId}/use-cases/prompts/bulk`,
|
|
3576
|
-
body: {
|
|
3774
|
+
body: {
|
|
3775
|
+
projectId: data.projectId,
|
|
3776
|
+
prompts: data.instructions.map((instruction) => ({ instruction }))
|
|
3777
|
+
}
|
|
3577
3778
|
};
|
|
3578
3779
|
}
|
|
3579
3780
|
},
|
|
@@ -3589,19 +3790,62 @@ var useCaseTools = [
|
|
|
3589
3790
|
body: { instruction: data.instruction }
|
|
3590
3791
|
};
|
|
3591
3792
|
}
|
|
3793
|
+
},
|
|
3794
|
+
{
|
|
3795
|
+
name: "muggle-remote-use-case-create",
|
|
3796
|
+
description: "Create a single use case from a fully-specified payload. Use this to persist use cases returned by muggle-remote-use-case-bulk-preview-submit \u2014 no LLM is invoked.",
|
|
3797
|
+
inputSchema: UseCaseCreateInputSchema,
|
|
3798
|
+
mapToUpstream: (input) => {
|
|
3799
|
+
const data = input;
|
|
3800
|
+
return {
|
|
3801
|
+
method: "POST",
|
|
3802
|
+
path: `${MUGGLE_TEST_PREFIX}/use-cases`,
|
|
3803
|
+
body: {
|
|
3804
|
+
projectId: data.projectId,
|
|
3805
|
+
title: data.title,
|
|
3806
|
+
description: data.description,
|
|
3807
|
+
userStory: data.userStory,
|
|
3808
|
+
url: data.url,
|
|
3809
|
+
useCaseBreakdown: data.useCaseBreakdown,
|
|
3810
|
+
status: data.status,
|
|
3811
|
+
priority: data.priority,
|
|
3812
|
+
source: data.source,
|
|
3813
|
+
category: data.category
|
|
3814
|
+
}
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
},
|
|
3818
|
+
{
|
|
3819
|
+
name: "muggle-remote-use-case-bulk-preview-submit",
|
|
3820
|
+
description: "Submit an async bulk-preview job that uses the OpenAI Batch API to generate use cases from many prompts at ~50% of normal LLM cost. Returns a job ID immediately; poll with muggle-remote-bulk-preview-job-get until the job reaches a terminal status, then persist each successful result via muggle-remote-use-case-create.",
|
|
3821
|
+
inputSchema: BulkPreviewSubmitUseCaseInputSchema,
|
|
3822
|
+
mapToUpstream: (input) => {
|
|
3823
|
+
const data = input;
|
|
3824
|
+
return {
|
|
3825
|
+
method: "POST",
|
|
3826
|
+
path: `${MUGGLE_TEST_PREFIX}/projects/${data.projectId}/use-cases/prompts/bulk-preview`,
|
|
3827
|
+
body: { prompts: data.prompts }
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3592
3830
|
}
|
|
3593
3831
|
];
|
|
3594
3832
|
var testCaseTools = [
|
|
3595
3833
|
{
|
|
3596
3834
|
name: "muggle-remote-test-case-list",
|
|
3597
|
-
description: "List test cases for a project.",
|
|
3835
|
+
description: "List test cases for a project. Returns up to 10 items per page by default (max 100). Response includes pagination metadata (totalCount, totalPages, hasMore) \u2014 check `hasMore` to decide whether to fetch additional pages.",
|
|
3598
3836
|
inputSchema: TestCaseListInputSchema,
|
|
3599
3837
|
mapToUpstream: (input) => {
|
|
3600
3838
|
const data = input;
|
|
3601
3839
|
return {
|
|
3602
3840
|
method: "GET",
|
|
3603
3841
|
path: `${MUGGLE_TEST_PREFIX}/test-cases`,
|
|
3604
|
-
queryParams: {
|
|
3842
|
+
queryParams: {
|
|
3843
|
+
projectId: data.projectId,
|
|
3844
|
+
page: data.page,
|
|
3845
|
+
pageSize: data.pageSize,
|
|
3846
|
+
sortBy: data.sortBy,
|
|
3847
|
+
sortOrder: data.sortOrder
|
|
3848
|
+
}
|
|
3605
3849
|
};
|
|
3606
3850
|
}
|
|
3607
3851
|
},
|
|
@@ -3668,44 +3912,95 @@ var testCaseTools = [
|
|
|
3668
3912
|
}
|
|
3669
3913
|
};
|
|
3670
3914
|
}
|
|
3915
|
+
},
|
|
3916
|
+
{
|
|
3917
|
+
name: "muggle-remote-test-case-bulk-preview-submit",
|
|
3918
|
+
description: "Submit an async bulk-preview job that uses the OpenAI Batch API to generate test cases for a single use case from many prompts at ~50% of normal LLM cost. Returns a job ID immediately; poll with muggle-remote-bulk-preview-job-get until the job reaches a terminal status, then persist each successful result via muggle-remote-test-case-create. Note: one input prompt may fan out to 1\u20135 test cases.",
|
|
3919
|
+
inputSchema: BulkPreviewSubmitTestCaseInputSchema,
|
|
3920
|
+
mapToUpstream: (input) => {
|
|
3921
|
+
const data = input;
|
|
3922
|
+
return {
|
|
3923
|
+
method: "POST",
|
|
3924
|
+
path: `${MUGGLE_TEST_PREFIX}/projects/${data.projectId}/use-cases/${data.useCaseId}/test-cases/prompts/bulk-preview`,
|
|
3925
|
+
body: { prompts: data.prompts }
|
|
3926
|
+
};
|
|
3927
|
+
}
|
|
3671
3928
|
}
|
|
3672
3929
|
];
|
|
3673
|
-
var
|
|
3930
|
+
var bulkPreviewTools = [
|
|
3674
3931
|
{
|
|
3675
|
-
name: "muggle-remote-
|
|
3676
|
-
description: "
|
|
3677
|
-
inputSchema:
|
|
3932
|
+
name: "muggle-remote-bulk-preview-job-get",
|
|
3933
|
+
description: "Get the current status and (when terminal) results of a bulk-preview job. Poll this after submitting a bulk-preview job \u2014 every 10\u201315 seconds is fine. Terminal statuses: succeeded, partial, failed, cancelled, expired.",
|
|
3934
|
+
inputSchema: BulkPreviewJobGetInputSchema,
|
|
3678
3935
|
mapToUpstream: (input) => {
|
|
3679
3936
|
const data = input;
|
|
3680
3937
|
return {
|
|
3681
3938
|
method: "GET",
|
|
3682
|
-
path: `${MUGGLE_TEST_PREFIX}/
|
|
3683
|
-
queryParams: { projectId: data.projectId, testCaseId: data.testCaseId, page: data.page, pageSize: data.pageSize }
|
|
3939
|
+
path: `${MUGGLE_TEST_PREFIX}/projects/${data.projectId}/bulk-preview-jobs/${data.jobId}`
|
|
3684
3940
|
};
|
|
3685
3941
|
}
|
|
3686
3942
|
},
|
|
3687
3943
|
{
|
|
3688
|
-
name: "muggle-remote-
|
|
3689
|
-
description: "
|
|
3690
|
-
inputSchema:
|
|
3944
|
+
name: "muggle-remote-bulk-preview-job-list",
|
|
3945
|
+
description: "List bulk-preview jobs for a project, optionally filtered by status or kind.",
|
|
3946
|
+
inputSchema: BulkPreviewJobListInputSchema,
|
|
3691
3947
|
mapToUpstream: (input) => {
|
|
3692
3948
|
const data = input;
|
|
3693
3949
|
return {
|
|
3694
3950
|
method: "GET",
|
|
3695
|
-
path: `${MUGGLE_TEST_PREFIX}/
|
|
3951
|
+
path: `${MUGGLE_TEST_PREFIX}/projects/${data.projectId}/bulk-preview-jobs`,
|
|
3952
|
+
queryParams: {
|
|
3953
|
+
status: data.status?.join(","),
|
|
3954
|
+
kind: data.kind,
|
|
3955
|
+
limit: data.limit,
|
|
3956
|
+
cursor: data.cursor
|
|
3957
|
+
}
|
|
3958
|
+
};
|
|
3959
|
+
}
|
|
3960
|
+
},
|
|
3961
|
+
{
|
|
3962
|
+
name: "muggle-remote-bulk-preview-job-cancel",
|
|
3963
|
+
description: "Request cancellation of a bulk-preview job. Cancellation is cooperative \u2014 the harvester picks it up on its next tick and moves the job to status=cancelled.",
|
|
3964
|
+
inputSchema: BulkPreviewJobCancelInputSchema,
|
|
3965
|
+
mapToUpstream: (input) => {
|
|
3966
|
+
const data = input;
|
|
3967
|
+
return {
|
|
3968
|
+
method: "DELETE",
|
|
3969
|
+
path: `${MUGGLE_TEST_PREFIX}/projects/${data.projectId}/bulk-preview-jobs/${data.jobId}`
|
|
3970
|
+
};
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
];
|
|
3974
|
+
var testScriptTools = [
|
|
3975
|
+
{
|
|
3976
|
+
name: "muggle-remote-test-script-list",
|
|
3977
|
+
description: "List test scripts for a project, optionally filtered by test case. Returns up to 10 items per page by default (max 100). Response includes pagination metadata (totalCount, totalPages, hasMore) \u2014 check `hasMore` to decide whether to fetch additional pages.",
|
|
3978
|
+
inputSchema: TestScriptListInputSchema,
|
|
3979
|
+
mapToUpstream: (input) => {
|
|
3980
|
+
const data = input;
|
|
3981
|
+
return {
|
|
3982
|
+
method: "GET",
|
|
3983
|
+
path: `${MUGGLE_TEST_PREFIX}/test-scripts`,
|
|
3984
|
+
queryParams: {
|
|
3985
|
+
projectId: data.projectId,
|
|
3986
|
+
testCaseId: data.testCaseId,
|
|
3987
|
+
page: data.page,
|
|
3988
|
+
pageSize: data.pageSize,
|
|
3989
|
+
sortBy: data.sortBy,
|
|
3990
|
+
sortOrder: data.sortOrder
|
|
3991
|
+
}
|
|
3696
3992
|
};
|
|
3697
3993
|
}
|
|
3698
3994
|
},
|
|
3699
3995
|
{
|
|
3700
|
-
name: "muggle-remote-test-script-
|
|
3701
|
-
description: "
|
|
3702
|
-
inputSchema:
|
|
3996
|
+
name: "muggle-remote-test-script-get",
|
|
3997
|
+
description: "Get details of a specific test script.",
|
|
3998
|
+
inputSchema: TestScriptGetInputSchema,
|
|
3703
3999
|
mapToUpstream: (input) => {
|
|
3704
4000
|
const data = input;
|
|
3705
4001
|
return {
|
|
3706
4002
|
method: "GET",
|
|
3707
|
-
path: `${MUGGLE_TEST_PREFIX}/test-scripts
|
|
3708
|
-
queryParams: { projectId: data.projectId, page: data.page, pageSize: data.pageSize }
|
|
4003
|
+
path: `${MUGGLE_TEST_PREFIX}/test-scripts/${data.testScriptId}`
|
|
3709
4004
|
};
|
|
3710
4005
|
}
|
|
3711
4006
|
}
|
|
@@ -4605,7 +4900,9 @@ var authTools = [
|
|
|
4605
4900
|
localHandler: async (input) => {
|
|
4606
4901
|
const data = input;
|
|
4607
4902
|
const authService = getAuthService();
|
|
4608
|
-
const deviceCodeResponse = await authService.startDeviceCodeFlow(
|
|
4903
|
+
const deviceCodeResponse = await authService.startDeviceCodeFlow({
|
|
4904
|
+
forceNewSession: data.forceNewSession
|
|
4905
|
+
});
|
|
4609
4906
|
const waitForCompletion = data.waitForCompletion ?? true;
|
|
4610
4907
|
if (!waitForCompletion) {
|
|
4611
4908
|
return {
|
|
@@ -4691,6 +4988,7 @@ var allQaToolDefinitions = [
|
|
|
4691
4988
|
...projectTools,
|
|
4692
4989
|
...useCaseTools,
|
|
4693
4990
|
...testCaseTools,
|
|
4991
|
+
...bulkPreviewTools,
|
|
4694
4992
|
...testScriptTools,
|
|
4695
4993
|
...actionScriptTools,
|
|
4696
4994
|
...workflowTools,
|
|
@@ -4897,12 +5195,10 @@ var ExecuteTestGenerationInputSchema = z.object({
|
|
|
4897
5195
|
testCase: TestCaseDetailsSchema.describe("Test case details obtained from muggle-remote-test-case-get"),
|
|
4898
5196
|
/** Local URL to test against. */
|
|
4899
5197
|
localUrl: z.string().url().describe("Local URL to test against (e.g., http://localhost:3000)"),
|
|
4900
|
-
/** Explicit approval to launch electron-app. */
|
|
4901
|
-
approveElectronAppLaunch: z.boolean().describe("Set to true after the user explicitly approves launching electron-app"),
|
|
4902
5198
|
/** Optional timeout. */
|
|
4903
5199
|
timeoutMs: z.number().int().positive().optional().describe("Timeout in milliseconds (default: 300000 = 5 min)"),
|
|
4904
|
-
/** Show the electron-app UI during execution.
|
|
4905
|
-
showUi: z.boolean().optional().describe("Show the electron-app UI during generation.
|
|
5200
|
+
/** 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.")
|
|
4906
5202
|
});
|
|
4907
5203
|
var ExecuteReplayInputSchema = z.object({
|
|
4908
5204
|
/** Test script metadata from muggle-remote-test-script-get. */
|
|
@@ -4911,12 +5207,10 @@ var ExecuteReplayInputSchema = z.object({
|
|
|
4911
5207
|
actionScript: z.array(z.unknown()).describe("Action script steps from muggle-remote-action-script-get"),
|
|
4912
5208
|
/** Local URL to test against. */
|
|
4913
5209
|
localUrl: z.string().url().describe("Local URL to test against (e.g., http://localhost:3000)"),
|
|
4914
|
-
/** Explicit approval to launch electron-app. */
|
|
4915
|
-
approveElectronAppLaunch: z.boolean().describe("Set to true after the user explicitly approves launching electron-app"),
|
|
4916
5210
|
/** Optional timeout. */
|
|
4917
5211
|
timeoutMs: z.number().int().positive().optional().describe("Timeout in milliseconds (default: 180000 = 3 min)"),
|
|
4918
|
-
/** Show the electron-app UI during execution.
|
|
4919
|
-
showUi: z.boolean().optional().describe("Show the electron-app UI during replay.
|
|
5212
|
+
/** 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.")
|
|
4920
5214
|
});
|
|
4921
5215
|
var CancelExecutionInputSchema = z.object({
|
|
4922
5216
|
runId: MuggleEntityIdSchema.describe("Run ID (UUID) to cancel")
|
|
@@ -5156,38 +5450,13 @@ var testScriptGetTool = {
|
|
|
5156
5450
|
};
|
|
5157
5451
|
var executeTestGenerationTool = {
|
|
5158
5452
|
name: "muggle-local-execute-test-generation",
|
|
5159
|
-
description: "Generate an end-to-end (E2E) acceptance test script by launching a real browser against your web app. The browser navigates your app, executes the test case steps (like signing up, filling forms, clicking through flows), and produces a replayable test script with screenshots. Use this to create new browser tests for any user flow. Requires a test case (from muggle-remote-test-case-get) and a localhost URL. Launches an Electron browser \u2014
|
|
5453
|
+
description: "Generate an end-to-end (E2E) acceptance test script by launching a real browser against your web app. The browser navigates your app, executes the test case steps (like signing up, filling forms, clicking through flows), and produces a replayable test script with screenshots. Use this to create new browser tests for any user flow. Requires a test case (from muggle-remote-test-case-get) and a localhost URL. Launches an Electron browser \u2014 defaults to a visible window; pass showUi: false to run headless.",
|
|
5160
5454
|
inputSchema: ExecuteTestGenerationInputSchema,
|
|
5161
5455
|
execute: async (ctx) => {
|
|
5162
5456
|
const logger14 = createChildLogger2(ctx.correlationId);
|
|
5163
5457
|
logger14.info("Executing muggle-local-execute-test-generation");
|
|
5164
5458
|
const input = ExecuteTestGenerationInputSchema.parse(ctx.input);
|
|
5165
|
-
|
|
5166
|
-
const showUiExplicit = input.showUi !== void 0;
|
|
5167
|
-
const uiMode = input.showUi === true ? "visible GUI (showUi: true)" : "headless (showUi: false or omitted)";
|
|
5168
|
-
return {
|
|
5169
|
-
content: [
|
|
5170
|
-
"## Electron App Launch Required",
|
|
5171
|
-
"",
|
|
5172
|
-
"This tool will launch the electron-app to generate a test script.",
|
|
5173
|
-
"Please set `approveElectronAppLaunch: true` to proceed.",
|
|
5174
|
-
"",
|
|
5175
|
-
"**Visible GUI:** Ask the user whether they want to watch the Electron window during generation.",
|
|
5176
|
-
"- If **yes** \u2192 when approving, pass `showUi: true`.",
|
|
5177
|
-
"- If **no** \u2192 when approving, pass `showUi: false` (or omit `showUi`; generation runs headless).",
|
|
5178
|
-
"",
|
|
5179
|
-
showUiExplicit ? `**Current choice:** ${uiMode}` : "**Current choice:** not set \u2014 default on approval is headless unless you pass `showUi: true`.",
|
|
5180
|
-
"",
|
|
5181
|
-
`**Test Case:** ${input.testCase.title}`,
|
|
5182
|
-
`**Local URL:** ${input.localUrl}`,
|
|
5183
|
-
"",
|
|
5184
|
-
"**Note:** The electron-app will navigate your test URL and record steps."
|
|
5185
|
-
].join("\n"),
|
|
5186
|
-
isError: false,
|
|
5187
|
-
data: { requiresApproval: true }
|
|
5188
|
-
};
|
|
5189
|
-
}
|
|
5190
|
-
const showUi = input.showUi === true;
|
|
5459
|
+
const showUi = input.showUi !== false;
|
|
5191
5460
|
try {
|
|
5192
5461
|
const result = await executeTestGeneration({
|
|
5193
5462
|
testCase: input.testCase,
|
|
@@ -5219,39 +5488,13 @@ var executeTestGenerationTool = {
|
|
|
5219
5488
|
};
|
|
5220
5489
|
var executeReplayTool = {
|
|
5221
5490
|
name: "muggle-local-execute-replay",
|
|
5222
|
-
description: "Replay an existing E2E acceptance test script in a real browser to verify your app still works correctly \u2014 use this for regression testing after code changes. The browser executes each saved step and captures screenshots so you can see what happened. Requires: (1) test script metadata from muggle-remote-test-script-get, (2) actionScript content from muggle-remote-action-script-get using the testScript.actionScriptId, and (3) a localhost URL. Launches an Electron browser \u2014
|
|
5491
|
+
description: "Replay an existing E2E acceptance test script in a real browser to verify your app still works correctly \u2014 use this for regression testing after code changes. The browser executes each saved step and captures screenshots so you can see what happened. Requires: (1) test script metadata from muggle-remote-test-script-get, (2) actionScript content from muggle-remote-action-script-get using the testScript.actionScriptId, and (3) a localhost URL. Launches an Electron browser \u2014 defaults to a visible window; pass showUi: false to run headless.",
|
|
5223
5492
|
inputSchema: ExecuteReplayInputSchema,
|
|
5224
5493
|
execute: async (ctx) => {
|
|
5225
5494
|
const logger14 = createChildLogger2(ctx.correlationId);
|
|
5226
5495
|
logger14.info("Executing muggle-local-execute-replay");
|
|
5227
5496
|
const input = ExecuteReplayInputSchema.parse(ctx.input);
|
|
5228
|
-
|
|
5229
|
-
const showUiExplicit = input.showUi !== void 0;
|
|
5230
|
-
const uiMode = input.showUi === true ? "visible GUI (showUi: true)" : "headless (showUi: false or omitted)";
|
|
5231
|
-
return {
|
|
5232
|
-
content: [
|
|
5233
|
-
"## Electron App Launch Required",
|
|
5234
|
-
"",
|
|
5235
|
-
"This tool will launch the electron-app to replay a test script.",
|
|
5236
|
-
"Please set `approveElectronAppLaunch: true` to proceed.",
|
|
5237
|
-
"",
|
|
5238
|
-
"**Visible GUI:** Ask the user whether they want to watch the Electron window during replay.",
|
|
5239
|
-
"- If **yes** \u2192 when approving, pass `showUi: true`.",
|
|
5240
|
-
"- If **no** \u2192 when approving, pass `showUi: false` (or omit `showUi`; replay runs headless).",
|
|
5241
|
-
"",
|
|
5242
|
-
showUiExplicit ? `**Current choice:** ${uiMode}` : "**Current choice:** not set \u2014 default on approval is headless unless you pass `showUi: true`.",
|
|
5243
|
-
"",
|
|
5244
|
-
`**Test Script:** ${input.testScript.name}`,
|
|
5245
|
-
`**Local URL:** ${input.localUrl}`,
|
|
5246
|
-
`**Steps:** ${input.actionScript.length}`,
|
|
5247
|
-
"",
|
|
5248
|
-
"**Note:** The electron-app will execute the test steps against your local URL."
|
|
5249
|
-
].join("\n"),
|
|
5250
|
-
isError: false,
|
|
5251
|
-
data: { requiresApproval: true }
|
|
5252
|
-
};
|
|
5253
|
-
}
|
|
5254
|
-
const showUi = input.showUi === true;
|
|
5497
|
+
const showUi = input.showUi !== false;
|
|
5255
5498
|
try {
|
|
5256
5499
|
const result = await executeReplay({
|
|
5257
5500
|
testScript: input.testScript,
|