@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.
Files changed (30) hide show
  1. package/README.md +31 -13
  2. package/dist/{chunk-PMI2DI3V.js → chunk-TP4T4T2Z.js} +348 -105
  3. package/dist/cli.js +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  6. package/dist/plugin/.cursor-plugin/plugin.json +1 -1
  7. package/dist/plugin/README.md +1 -0
  8. package/dist/plugin/skills/do/e2e-acceptance.md +6 -3
  9. package/dist/plugin/skills/do/open-prs.md +35 -74
  10. package/dist/plugin/skills/muggle-pr-visual-walkthrough/SKILL.md +181 -0
  11. package/dist/plugin/skills/muggle-test/SKILL.md +146 -121
  12. package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +66 -16
  13. package/dist/plugin/skills/muggle-test-import/SKILL.md +127 -25
  14. package/dist/plugin/skills/muggle-test-regenerate-missing/SKILL.md +201 -0
  15. package/dist/plugin/skills/muggle-test-regenerate-missing/evals/evals.json +58 -0
  16. package/dist/plugin/skills/muggle-test-regenerate-missing/evals/trigger-eval.json +22 -0
  17. package/dist/release-manifest.json +7 -0
  18. package/package.json +7 -7
  19. package/plugin/.claude-plugin/plugin.json +1 -1
  20. package/plugin/.cursor-plugin/plugin.json +1 -1
  21. package/plugin/README.md +1 -0
  22. package/plugin/skills/do/e2e-acceptance.md +6 -3
  23. package/plugin/skills/do/open-prs.md +35 -74
  24. package/plugin/skills/muggle-pr-visual-walkthrough/SKILL.md +181 -0
  25. package/plugin/skills/muggle-test/SKILL.md +146 -121
  26. package/plugin/skills/muggle-test-feature-local/SKILL.md +66 -16
  27. package/plugin/skills/muggle-test-import/SKILL.md +127 -25
  28. package/plugin/skills/muggle-test-regenerate-missing/SKILL.md +201 -0
  29. package/plugin/skills/muggle-test-regenerate-missing/evals/evals.json +58 -0
  30. 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, rmSync, mkdirSync, readdirSync, createWriteStream, writeFileSync, statSync } from 'fs';
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, join } from 'path';
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: "1.0.0",
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: data.verification_uri_complete
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
- verificationUriComplete: data.verification_uri_complete
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: data.verification_uri_complete
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().optional().describe("Page number (1-based)"),
2858
- pageSize: z.number().int().positive().max(100).optional().describe("Number of items per page")
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
- prompts: z.array(z.object({
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 all projects accessible to the authenticated user.",
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: { page: data.page, pageSize: data.pageSize }
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 all use cases for a project.",
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: { projectId: data.projectId, page: data.page, pageSize: data.pageSize }
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: { projectId: data.projectId, prompts: data.prompts }
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: { projectId: data.projectId, page: data.page, pageSize: data.pageSize }
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 testScriptTools = [
3930
+ var bulkPreviewTools = [
3674
3931
  {
3675
- name: "muggle-remote-test-script-list",
3676
- description: "List test scripts for a project, optionally filtered by test case.",
3677
- inputSchema: TestScriptListInputSchema,
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}/test-scripts`,
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-test-script-get",
3689
- description: "Get details of a specific test script.",
3690
- inputSchema: TestScriptGetInputSchema,
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}/test-scripts/${data.testScriptId}`
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-list-paginated",
3701
- description: "List test scripts with full pagination support.",
3702
- inputSchema: TestScriptListPaginatedInputSchema,
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/paginated`,
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. Ask the user before approving; true = visible window, false or omit = headless. */
4905
- showUi: z.boolean().optional().describe("Show the electron-app UI during generation. Ask the user: true to watch the window, false or omit for headless.")
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. Ask the user before approving; true = visible window, false or omit = headless. */
4919
- showUi: z.boolean().optional().describe("Show the electron-app UI during replay. Ask the user: true to watch the window, false or omit for headless.")
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 requires explicit approval via approveElectronAppLaunch. Before approving, ask the user whether they want a visible GUI; pass showUi: true to watch the window or showUi: false for headless (default when omitted).",
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
- if (!input.approveElectronAppLaunch) {
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 requires explicit approval via approveElectronAppLaunch. Before approving, ask the user whether they want a visible GUI; pass showUi: true to watch the window or showUi: false for headless (default when omitted).",
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
- if (!input.approveElectronAppLaunch) {
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,