@muggleai/works 4.0.1 → 4.0.3

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.
@@ -1,5 +1,5 @@
1
1
  import * as fs3 from 'fs';
2
- import { readFileSync, existsSync, rmSync, mkdirSync, createWriteStream, readdirSync, statSync, writeFileSync } from 'fs';
2
+ import { readFileSync, existsSync, rmSync, mkdirSync, readdirSync, createWriteStream, writeFileSync, 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';
@@ -7,7 +7,7 @@ import { dirname, resolve, join } from 'path';
7
7
  import { fileURLToPath } from 'url';
8
8
  import winston from 'winston';
9
9
  import axios, { AxiosError } from 'axios';
10
- import { spawn, exec } from 'child_process';
10
+ import { spawn, exec, execFile } from 'child_process';
11
11
  import * as fs5 from 'fs/promises';
12
12
  import { z, ZodError } from 'zod';
13
13
  import * as crypto from 'crypto';
@@ -44,7 +44,7 @@ var DEFAULT_AUTH0_PRODUCTION_DOMAIN = "login.muggle-ai.com";
44
44
  var DEFAULT_AUTH0_PRODUCTION_CLIENT_ID = "UgG5UjoyLksxMciWWKqVpwfWrJ4rFvtT";
45
45
  var DEFAULT_AUTH0_PRODUCTION_AUDIENCE = "https://muggleai.us.auth0.com/api/v2/";
46
46
  var DEFAULT_AUTH0_DEV_DOMAIN = "dev-po4mxmz0rd8a0w8w.us.auth0.com";
47
- var DEFAULT_AUTH0_DEV_CLIENT_ID = "hihMM2cxb40yHaZMH2MMXwO2ZRJQ3MxA";
47
+ var DEFAULT_AUTH0_DEV_CLIENT_ID = "GBvkMdTbCI80XJXnJ90MmbEvXwcWGUtw";
48
48
  var DEFAULT_AUTH0_DEV_AUDIENCE = "https://dev-po4mxmz0rd8a0w8w.us.auth0.com/api/v2/";
49
49
  var DEFAULT_AUTH0_SCOPE = "openid profile email offline_access";
50
50
  var configInstance = null;
@@ -130,8 +130,7 @@ function getDataDir2() {
130
130
  }
131
131
  function getDownloadedElectronAppPath() {
132
132
  const platformName = os3.platform();
133
- const config = getMuggleConfig();
134
- const version = config.electronAppVersion;
133
+ const version = getElectronAppVersion();
135
134
  const baseDir = path2.join(getDataDir2(), ELECTRON_APP_DIR, version);
136
135
  let binaryPath;
137
136
  switch (platformName) {
@@ -1865,9 +1864,24 @@ function getElectronAppPathOrThrow() {
1865
1864
  const config = getConfig();
1866
1865
  const electronAppPath = config.localQa.electronAppPath;
1867
1866
  if (!electronAppPath || electronAppPath.trim() === "") {
1868
- throw new Error(
1869
- "Electron app binary not found. Run 'muggle setup' or set ELECTRON_APP_PATH."
1870
- );
1867
+ const version = getElectronAppVersion();
1868
+ const versionDir = getElectronAppDir(version);
1869
+ const envPath = process.env.ELECTRON_APP_PATH;
1870
+ const errorLines = [
1871
+ "Electron app binary not found.",
1872
+ "",
1873
+ ` Expected version: ${version}`,
1874
+ ` Checked directory: ${versionDir}`
1875
+ ];
1876
+ if (envPath) {
1877
+ errorLines.push(` ELECTRON_APP_PATH: ${envPath} (not found or invalid)`);
1878
+ } else {
1879
+ errorLines.push(" ELECTRON_APP_PATH: (not set)");
1880
+ }
1881
+ errorLines.push("");
1882
+ errorLines.push("To fix this, run: muggle setup");
1883
+ errorLines.push("Or set ELECTRON_APP_PATH to the path of the MuggleAI executable.");
1884
+ throw new Error(errorLines.join("\n"));
1871
1885
  }
1872
1886
  return electronAppPath;
1873
1887
  }
@@ -3159,17 +3173,17 @@ var PromptServiceClient = class {
3159
3173
  * @param path - Path to validate.
3160
3174
  * @throws GatewayError if path is not allowed.
3161
3175
  */
3162
- validatePath(path13) {
3163
- const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) => path13.startsWith(prefix));
3176
+ validatePath(path15) {
3177
+ const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) => path15.startsWith(prefix));
3164
3178
  if (!isAllowed) {
3165
3179
  const logger14 = getLogger();
3166
3180
  logger14.error("Path not in allowlist", {
3167
- path: path13,
3181
+ path: path15,
3168
3182
  allowedPrefixes: ALLOWED_UPSTREAM_PREFIXES
3169
3183
  });
3170
3184
  throw new GatewayError({
3171
3185
  code: "FORBIDDEN" /* FORBIDDEN */,
3172
- message: `Path '${path13}' is not allowed`
3186
+ message: `Path '${path15}' is not allowed`
3173
3187
  });
3174
3188
  }
3175
3189
  }
@@ -5796,8 +5810,8 @@ function createUnifiedMcpServer(options) {
5796
5810
  errors: error.issues
5797
5811
  });
5798
5812
  const issueMessages = error.issues.slice(0, 3).map((issue) => {
5799
- const path13 = issue.path.join(".");
5800
- return path13 ? `'${path13}': ${issue.message}` : issue.message;
5813
+ const path15 = issue.path.join(".");
5814
+ return path15 ? `'${path15}': ${issue.message}` : issue.message;
5801
5815
  });
5802
5816
  return {
5803
5817
  content: [
@@ -6065,6 +6079,72 @@ var logger8 = getLogger();
6065
6079
  function getCursorMcpConfigPath() {
6066
6080
  return join(homedir(), ".cursor", "mcp.json");
6067
6081
  }
6082
+ function getExpectedExecutablePath(versionDir) {
6083
+ const os4 = platform();
6084
+ switch (os4) {
6085
+ case "darwin":
6086
+ return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
6087
+ case "win32":
6088
+ return path2.join(versionDir, "MuggleAI.exe");
6089
+ case "linux":
6090
+ return path2.join(versionDir, "MuggleAI");
6091
+ default:
6092
+ throw new Error(`Unsupported platform: ${os4}`);
6093
+ }
6094
+ }
6095
+ function verifyElectronAppInstallation() {
6096
+ const version = getElectronAppVersion();
6097
+ const versionDir = getElectronAppDir(version);
6098
+ const executablePath = getExpectedExecutablePath(versionDir);
6099
+ const metadataPath = path2.join(versionDir, ".install-metadata.json");
6100
+ const result = {
6101
+ valid: false,
6102
+ versionDir,
6103
+ executablePath,
6104
+ executableExists: false,
6105
+ executableIsFile: false,
6106
+ metadataExists: false,
6107
+ hasPartialArchive: false
6108
+ };
6109
+ if (!fs3.existsSync(versionDir)) {
6110
+ result.errorDetail = "Version directory does not exist";
6111
+ return result;
6112
+ }
6113
+ const archivePatterns = ["MuggleAI-darwin", "MuggleAI-win32", "MuggleAI-linux"];
6114
+ try {
6115
+ const files = fs3.readdirSync(versionDir);
6116
+ for (const file of files) {
6117
+ if (archivePatterns.some((pattern) => file.startsWith(pattern)) && (file.endsWith(".zip") || file.endsWith(".tar.gz"))) {
6118
+ result.hasPartialArchive = true;
6119
+ break;
6120
+ }
6121
+ }
6122
+ } catch {
6123
+ }
6124
+ result.executableExists = fs3.existsSync(executablePath);
6125
+ if (!result.executableExists) {
6126
+ if (result.hasPartialArchive) {
6127
+ result.errorDetail = "Download incomplete: archive found but not extracted";
6128
+ } else {
6129
+ result.errorDetail = "Executable not found at expected path";
6130
+ }
6131
+ return result;
6132
+ }
6133
+ try {
6134
+ const stats = fs3.statSync(executablePath);
6135
+ result.executableIsFile = stats.isFile();
6136
+ if (!result.executableIsFile) {
6137
+ result.errorDetail = "Executable path exists but is not a file";
6138
+ return result;
6139
+ }
6140
+ } catch {
6141
+ result.errorDetail = "Cannot stat executable (broken symlink?)";
6142
+ return result;
6143
+ }
6144
+ result.metadataExists = fs3.existsSync(metadataPath);
6145
+ result.valid = true;
6146
+ return result;
6147
+ }
6068
6148
  function validateCursorMcpConfig() {
6069
6149
  const cursorMcpConfigPath = getCursorMcpConfigPath();
6070
6150
  if (!existsSync(cursorMcpConfigPath)) {
@@ -6140,12 +6220,13 @@ function runDiagnostics() {
6140
6220
  description: existsSync(dataDir) ? `Found at ${dataDir}` : `Not found at ${dataDir}`,
6141
6221
  suggestion: "Run 'muggle login' to create the data directory"
6142
6222
  });
6143
- const electronInstalled = isElectronAppInstalled();
6144
6223
  const electronVersion = getElectronAppVersion();
6145
6224
  const bundledVersion = getBundledElectronAppVersion();
6146
6225
  const versionSource = getElectronAppVersionSource();
6226
+ const installVerification = verifyElectronAppInstallation();
6147
6227
  let electronDescription;
6148
- if (electronInstalled) {
6228
+ let electronSuggestion;
6229
+ if (installVerification.valid) {
6149
6230
  electronDescription = `Installed (v${electronVersion})`;
6150
6231
  switch (versionSource) {
6151
6232
  case "env":
@@ -6155,16 +6236,30 @@ function runDiagnostics() {
6155
6236
  electronDescription += ` [overridden from bundled v${bundledVersion}]`;
6156
6237
  break;
6157
6238
  }
6239
+ if (!installVerification.metadataExists) {
6240
+ electronDescription += " [missing metadata]";
6241
+ }
6158
6242
  } else {
6159
6243
  electronDescription = `Not installed (expected v${electronVersion})`;
6244
+ if (installVerification.errorDetail) {
6245
+ electronDescription += `
6246
+ \u2514\u2500 ${installVerification.errorDetail}`;
6247
+ electronDescription += `
6248
+ \u2514\u2500 Checked: ${installVerification.versionDir}`;
6249
+ }
6250
+ if (installVerification.hasPartialArchive) {
6251
+ electronSuggestion = "Run 'muggle setup --force' to re-download and extract";
6252
+ } else {
6253
+ electronSuggestion = "Run 'muggle setup' to download the Electron app";
6254
+ }
6160
6255
  }
6161
6256
  results.push({
6162
6257
  name: "Electron App",
6163
- passed: electronInstalled,
6258
+ passed: installVerification.valid,
6164
6259
  description: electronDescription,
6165
- suggestion: "Run 'muggle setup' to download the Electron app"
6260
+ suggestion: electronSuggestion
6166
6261
  });
6167
- if (electronInstalled) {
6262
+ if (installVerification.valid) {
6168
6263
  results.push({
6169
6264
  name: "Electron App Updates",
6170
6265
  passed: true,
@@ -6266,8 +6361,8 @@ ${title}`, COLORS.bold + COLORS.cyan);
6266
6361
  function cmd(cmd2) {
6267
6362
  return colorize(cmd2, COLORS.green);
6268
6363
  }
6269
- function path11(path13) {
6270
- return colorize(path13, COLORS.yellow);
6364
+ function path12(path15) {
6365
+ return colorize(path15, COLORS.yellow);
6271
6366
  }
6272
6367
  function getHelpGuidance() {
6273
6368
  const lines = [
@@ -6282,14 +6377,14 @@ function getHelpGuidance() {
6282
6377
  " assistants with tools to perform automated QA testing of web applications.",
6283
6378
  "",
6284
6379
  " It supports both:",
6285
- ` ${colorize("\u2022", COLORS.green)} Cloud QA - Test remote production/staging sites`,
6380
+ ` ${colorize("\u2022", COLORS.green)} Cloud QA - Test remote production/staging sites with a public URL`,
6286
6381
  ` ${colorize("\u2022", COLORS.green)} Local QA - Test localhost development servers`,
6287
6382
  "",
6288
6383
  header("Setup Instructions"),
6289
6384
  "",
6290
6385
  ` ${colorize("Step 1:", COLORS.bold)} Configure your MCP client`,
6291
6386
  "",
6292
- ` For ${colorize("Cursor", COLORS.bold)}, edit ${path11("~/.cursor/mcp.json")}:`,
6387
+ ` For ${colorize("Cursor", COLORS.bold)}, edit ${path12("~/.cursor/mcp.json")}:`,
6293
6388
  "",
6294
6389
  ` ${colorize("{", COLORS.dim)}`,
6295
6390
  ` ${colorize('"mcpServers"', COLORS.yellow)}: {`,
@@ -6308,7 +6403,6 @@ function getHelpGuidance() {
6308
6403
  header("CLI Commands"),
6309
6404
  "",
6310
6405
  ` ${colorize("Server Commands:", COLORS.bold)}`,
6311
- ` ${cmd("muggle")} Start MCP server (default)`,
6312
6406
  ` ${cmd("muggle serve")} Start MCP server with all tools`,
6313
6407
  ` ${cmd("muggle serve --qa")} Start with Cloud QA tools only`,
6314
6408
  ` ${cmd("muggle serve --local")} Start with Local QA tools only`,
@@ -6344,7 +6438,7 @@ function getHelpGuidance() {
6344
6438
  ` 2. ${colorize("Log in", COLORS.bold)} with your Muggle AI account`,
6345
6439
  ` 3. ${colorize("The tool call continues", COLORS.bold)} with your credentials`,
6346
6440
  "",
6347
- ` Credentials are stored in ${path11("~/.muggle-ai/credentials.json")}`,
6441
+ ` Credentials are stored in ${path12("~/.muggle-ai/credentials.json")}`,
6348
6442
  "",
6349
6443
  header("Available MCP Tools"),
6350
6444
  "",
@@ -6359,12 +6453,12 @@ function getHelpGuidance() {
6359
6453
  "",
6360
6454
  header("Data Directory"),
6361
6455
  "",
6362
- ` All data is stored in ${path11("~/.muggle-ai/")}:`,
6456
+ ` All data is stored in ${path12("~/.muggle-ai/")}:`,
6363
6457
  "",
6364
- ` ${path11("credentials.json")} Auth credentials (auto-generated)`,
6365
- ` ${path11("projects/")} Local test projects`,
6366
- ` ${path11("sessions/")} Test execution sessions`,
6367
- ` ${path11("electron-app/")} Downloaded Electron app binaries`,
6458
+ ` ${path12("credentials.json")} Auth credentials (auto-generated)`,
6459
+ ` ${path12("projects/")} Local test projects`,
6460
+ ` ${path12("sessions/")} Test execution sessions`,
6461
+ ` ${path12("electron-app/")} Downloaded Electron app binaries`,
6368
6462
  "",
6369
6463
  header("Troubleshooting"),
6370
6464
  "",
@@ -6499,6 +6593,9 @@ async function serveCommand(options) {
6499
6593
  }
6500
6594
  }
6501
6595
  var logger11 = getLogger();
6596
+ var MAX_RETRY_ATTEMPTS = 3;
6597
+ var RETRY_BASE_DELAY_MS = 2e3;
6598
+ var INSTALL_METADATA_FILE_NAME = ".install-metadata.json";
6502
6599
  function getBinaryName() {
6503
6600
  const os4 = platform();
6504
6601
  const architecture = arch();
@@ -6515,21 +6612,47 @@ function getBinaryName() {
6515
6612
  throw new Error(`Unsupported platform: ${os4}`);
6516
6613
  }
6517
6614
  }
6615
+ function getExpectedExecutablePath2(versionDir) {
6616
+ const os4 = platform();
6617
+ switch (os4) {
6618
+ case "darwin":
6619
+ return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
6620
+ case "win32":
6621
+ return path2.join(versionDir, "MuggleAI.exe");
6622
+ case "linux":
6623
+ return path2.join(versionDir, "MuggleAI");
6624
+ default:
6625
+ throw new Error(`Unsupported platform: ${os4}`);
6626
+ }
6627
+ }
6518
6628
  async function extractZip(zipPath, destDir) {
6519
6629
  return new Promise((resolve4, reject) => {
6520
- const cmd2 = platform() === "win32" ? `powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"` : `unzip -o "${zipPath}" -d "${destDir}"`;
6521
- exec(cmd2, (error) => {
6522
- if (error) {
6523
- reject(error);
6524
- } else {
6525
- resolve4();
6526
- }
6527
- });
6630
+ if (platform() === "win32") {
6631
+ execFile(
6632
+ "powershell",
6633
+ ["-command", `Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force`],
6634
+ (error) => {
6635
+ if (error) {
6636
+ reject(error);
6637
+ } else {
6638
+ resolve4();
6639
+ }
6640
+ }
6641
+ );
6642
+ } else {
6643
+ execFile("unzip", ["-o", zipPath, "-d", destDir], (error) => {
6644
+ if (error) {
6645
+ reject(error);
6646
+ } else {
6647
+ resolve4();
6648
+ }
6649
+ });
6650
+ }
6528
6651
  });
6529
6652
  }
6530
6653
  async function extractTarGz(tarPath, destDir) {
6531
6654
  return new Promise((resolve4, reject) => {
6532
- exec(`tar -xzf "${tarPath}" -C "${destDir}"`, (error) => {
6655
+ execFile("tar", ["-xzf", tarPath, "-C", destDir], (error) => {
6533
6656
  if (error) {
6534
6657
  reject(error);
6535
6658
  } else {
@@ -6538,10 +6661,66 @@ async function extractTarGz(tarPath, destDir) {
6538
6661
  });
6539
6662
  });
6540
6663
  }
6664
+ function sleep2(ms) {
6665
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
6666
+ }
6667
+ async function downloadWithRetry(downloadUrl, destPath) {
6668
+ let lastError = null;
6669
+ for (let attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
6670
+ try {
6671
+ if (attempt > 1) {
6672
+ const delayMs = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 2);
6673
+ console.log(`Retry attempt ${attempt}/${MAX_RETRY_ATTEMPTS} after ${delayMs}ms delay...`);
6674
+ await sleep2(delayMs);
6675
+ }
6676
+ const response = await fetch(downloadUrl);
6677
+ if (!response.ok) {
6678
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
6679
+ }
6680
+ if (!response.body) {
6681
+ throw new Error("No response body received");
6682
+ }
6683
+ const fileStream = createWriteStream(destPath);
6684
+ await pipeline(response.body, fileStream);
6685
+ return true;
6686
+ } catch (error) {
6687
+ lastError = error instanceof Error ? error : new Error(String(error));
6688
+ console.error(`Download attempt ${attempt} failed: ${lastError.message}`);
6689
+ if (existsSync(destPath)) {
6690
+ rmSync(destPath, { force: true });
6691
+ }
6692
+ }
6693
+ }
6694
+ if (lastError) {
6695
+ throw new Error(`Download failed after ${MAX_RETRY_ATTEMPTS} attempts: ${lastError.message}`);
6696
+ }
6697
+ return false;
6698
+ }
6699
+ function writeInstallMetadata(params) {
6700
+ const metadata = {
6701
+ version: params.version,
6702
+ binaryName: params.binaryName,
6703
+ platformKey: params.platformKey,
6704
+ executableChecksum: params.executableChecksum,
6705
+ expectedArchiveChecksum: params.expectedArchiveChecksum,
6706
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6707
+ };
6708
+ writeFileSync(params.metadataPath, `${JSON.stringify(metadata, null, 2)}
6709
+ `, "utf-8");
6710
+ }
6711
+ function cleanupFailedInstall(versionDir) {
6712
+ if (existsSync(versionDir)) {
6713
+ try {
6714
+ rmSync(versionDir, { recursive: true, force: true });
6715
+ } catch {
6716
+ }
6717
+ }
6718
+ }
6541
6719
  async function setupCommand(options) {
6542
6720
  const version = getElectronAppVersion();
6543
6721
  const baseUrl = getDownloadBaseUrl();
6544
6722
  const versionDir = getElectronAppDir(version);
6723
+ const platformKey = getPlatformKey();
6545
6724
  if (!options.force && isElectronAppInstalled()) {
6546
6725
  console.log(`Electron app v${version} is already installed at ${versionDir}`);
6547
6726
  console.log("Use --force to re-download.");
@@ -6556,22 +6735,14 @@ async function setupCommand(options) {
6556
6735
  rmSync(versionDir, { recursive: true, force: true });
6557
6736
  }
6558
6737
  mkdirSync(versionDir, { recursive: true });
6559
- const response = await fetch(downloadUrl);
6560
- if (!response.ok) {
6561
- throw new Error(`Download failed: ${response.status} ${response.statusText}`);
6562
- }
6563
- const tempFile = `${versionDir}/${binaryName}`;
6564
- const fileStream = createWriteStream(tempFile);
6565
- if (!response.body) {
6566
- throw new Error("No response body");
6567
- }
6568
- await pipeline(response.body, fileStream);
6738
+ const tempFile = path2.join(versionDir, binaryName);
6739
+ await downloadWithRetry(downloadUrl, tempFile);
6569
6740
  console.log("Download complete, verifying checksum...");
6570
6741
  const checksums = getElectronAppChecksums();
6571
6742
  const expectedChecksum = getChecksumForPlatform(checksums);
6572
6743
  const checksumResult = await verifyFileChecksum(tempFile, expectedChecksum);
6573
6744
  if (!checksumResult.valid && expectedChecksum) {
6574
- rmSync(versionDir, { recursive: true, force: true });
6745
+ cleanupFailedInstall(versionDir);
6575
6746
  throw new Error(
6576
6747
  `Checksum verification failed!
6577
6748
  Expected: ${checksumResult.expected}
@@ -6590,6 +6761,25 @@ The downloaded file may be corrupted or tampered with.`
6590
6761
  } else if (binaryName.endsWith(".tar.gz")) {
6591
6762
  await extractTarGz(tempFile, versionDir);
6592
6763
  }
6764
+ const executablePath = getExpectedExecutablePath2(versionDir);
6765
+ if (!existsSync(executablePath)) {
6766
+ cleanupFailedInstall(versionDir);
6767
+ throw new Error(
6768
+ `Extraction failed: executable not found at expected path.
6769
+ Expected: ${executablePath}
6770
+ The archive may be corrupted or in an unexpected format.`
6771
+ );
6772
+ }
6773
+ const executableChecksum = await calculateFileChecksum(executablePath);
6774
+ const metadataPath = path2.join(versionDir, INSTALL_METADATA_FILE_NAME);
6775
+ writeInstallMetadata({
6776
+ metadataPath,
6777
+ version,
6778
+ binaryName,
6779
+ platformKey,
6780
+ executableChecksum,
6781
+ expectedArchiveChecksum: expectedChecksum
6782
+ });
6593
6783
  rmSync(tempFile, { force: true });
6594
6784
  console.log(`Electron app installed to ${versionDir}`);
6595
6785
  logger11.info("Setup complete", { version, path: versionDir });
@@ -6602,6 +6792,7 @@ The downloaded file may be corrupted or tampered with.`
6602
6792
  }
6603
6793
  var logger12 = getLogger();
6604
6794
  var GITHUB_RELEASES_API = "https://api.github.com/repos/multiplex-ai/muggle-ai-works/releases";
6795
+ var INSTALL_METADATA_FILE_NAME2 = ".install-metadata.json";
6605
6796
  var VERSION_OVERRIDE_FILE2 = "electron-app-version-override.json";
6606
6797
  function getBinaryName2() {
6607
6798
  const os4 = platform();
@@ -6704,21 +6895,59 @@ function compareVersions2(a, b) {
6704
6895
  }
6705
6896
  return 0;
6706
6897
  }
6898
+ function getExpectedExecutablePath3(versionDir) {
6899
+ const os4 = platform();
6900
+ switch (os4) {
6901
+ case "darwin":
6902
+ return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
6903
+ case "win32":
6904
+ return path2.join(versionDir, "MuggleAI.exe");
6905
+ case "linux":
6906
+ return path2.join(versionDir, "MuggleAI");
6907
+ default:
6908
+ throw new Error(`Unsupported platform: ${os4}`);
6909
+ }
6910
+ }
6911
+ function writeInstallMetadata2(params) {
6912
+ const metadata = {
6913
+ version: params.version,
6914
+ binaryName: params.binaryName,
6915
+ platformKey: params.platformKey,
6916
+ executableChecksum: params.executableChecksum,
6917
+ expectedArchiveChecksum: params.expectedArchiveChecksum,
6918
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6919
+ };
6920
+ writeFileSync(params.metadataPath, `${JSON.stringify(metadata, null, 2)}
6921
+ `, "utf-8");
6922
+ }
6707
6923
  async function extractZip2(zipPath, destDir) {
6708
6924
  return new Promise((resolve4, reject) => {
6709
- const cmd2 = platform() === "win32" ? `powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"` : `unzip -o "${zipPath}" -d "${destDir}"`;
6710
- exec(cmd2, (error) => {
6711
- if (error) {
6712
- reject(error);
6713
- } else {
6714
- resolve4();
6715
- }
6716
- });
6925
+ if (platform() === "win32") {
6926
+ execFile(
6927
+ "powershell",
6928
+ ["-command", `Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force`],
6929
+ (error) => {
6930
+ if (error) {
6931
+ reject(error);
6932
+ } else {
6933
+ resolve4();
6934
+ }
6935
+ }
6936
+ );
6937
+ } else {
6938
+ execFile("unzip", ["-o", zipPath, "-d", destDir], (error) => {
6939
+ if (error) {
6940
+ reject(error);
6941
+ } else {
6942
+ resolve4();
6943
+ }
6944
+ });
6945
+ }
6717
6946
  });
6718
6947
  }
6719
6948
  async function extractTarGz2(tarPath, destDir) {
6720
6949
  return new Promise((resolve4, reject) => {
6721
- exec(`tar -xzf "${tarPath}" -C "${destDir}"`, (error) => {
6950
+ execFile("tar", ["-xzf", tarPath, "-C", destDir], (error) => {
6722
6951
  if (error) {
6723
6952
  reject(error);
6724
6953
  } else {
@@ -6776,6 +7005,7 @@ async function fetchChecksumFromRelease(version) {
6776
7005
  async function downloadAndInstall(version, downloadUrl, checksum) {
6777
7006
  const versionDir = getElectronAppDir(version);
6778
7007
  const binaryName = getBinaryName2();
7008
+ const platformKey = getPlatformKey();
6779
7009
  console.log(`Downloading Muggle Test Electron app v${version}...`);
6780
7010
  console.log(`URL: ${downloadUrl}`);
6781
7011
  if (existsSync(versionDir)) {
@@ -6818,6 +7048,25 @@ The downloaded file may be corrupted or tampered with.`
6818
7048
  } else if (binaryName.endsWith(".tar.gz")) {
6819
7049
  await extractTarGz2(tempFile, versionDir);
6820
7050
  }
7051
+ const executablePath = getExpectedExecutablePath3(versionDir);
7052
+ if (!existsSync(executablePath)) {
7053
+ rmSync(versionDir, { recursive: true, force: true });
7054
+ throw new Error(
7055
+ `Extraction failed: executable not found at expected path.
7056
+ Expected: ${executablePath}
7057
+ The archive may be corrupted or in an unexpected format.`
7058
+ );
7059
+ }
7060
+ const executableChecksum = await calculateFileChecksum(executablePath);
7061
+ const metadataPath = path2.join(versionDir, INSTALL_METADATA_FILE_NAME2);
7062
+ writeInstallMetadata2({
7063
+ metadataPath,
7064
+ version,
7065
+ binaryName,
7066
+ platformKey,
7067
+ executableChecksum,
7068
+ expectedArchiveChecksum: expectedChecksum || ""
7069
+ });
6821
7070
  rmSync(tempFile, { force: true });
6822
7071
  saveVersionOverride(version);
6823
7072
  console.log(`Electron app v${version} installed to ${versionDir}`);
@@ -6900,7 +7149,11 @@ function createProgram() {
6900
7149
  program.command("logout").description("Clear stored credentials").action(logoutCommand);
6901
7150
  program.command("status").description("Show authentication status").action(statusCommand);
6902
7151
  program.action(() => {
6903
- serveCommand({ });
7152
+ helpCommand();
7153
+ });
7154
+ program.on("command:*", () => {
7155
+ helpCommand();
7156
+ process.exit(1);
6904
7157
  });
6905
7158
  return program;
6906
7159
  }
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './chunk-AJKZXT7B.js';
2
+ import { runCli } from './chunk-BQZQDOXI.js';
3
3
 
4
4
  // src/cli/main.ts
5
5
  runCli().catch((error) => {
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { src_exports2 as commands, createChildLogger, createUnifiedMcpServer, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, qa_exports as qa, server_exports as server, src_exports as shared } from './chunk-AJKZXT7B.js';
1
+ export { src_exports2 as commands, createChildLogger, createUnifiedMcpServer, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, qa_exports as qa, server_exports as server, src_exports as shared } from './chunk-BQZQDOXI.js';
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "muggle",
3
3
  "description": "Run real-browser QA 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.0.1",
4
+ "version": "4.0.3",
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 QA that validates your app's user experience — from Claude Code and Cursor to PR.",
5
- "version": "4.0.1",
5
+ "version": "4.0.3",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: muggle-test-feature-local
3
- description: Test a feature's user experience on localhost. Use when user types muggle test-feature-local, test my app, run QA, or validate UI changes.
3
+ description: Run a real-browser QA test against localhost to verify a feature works correctly — signup flows, checkout, form validation, UI interactions, or any user-facing behavior. Launches a browser that executes test steps and captures screenshots. Use this skill whenever the user asks to test, QA, validate, or verify their web app, UI changes, user flows, or frontend behavior on localhost or a dev server — even if they don't mention 'muggle' or 'QA' explicitly.
4
4
  ---
5
5
 
6
6
  # Muggle Test Feature Local
@@ -12,54 +12,111 @@ Run end-to-end feature testing from UI against a local URL:
12
12
 
13
13
  ## Workflow
14
14
 
15
- 1. **Auth**
16
- - `muggle-remote-auth-status`
17
- - If needed: `muggle-remote-auth-login` + `muggle-remote-auth-poll`
18
-
19
- 2. **Select or create the project -> use case -> test case to use**
20
- - Explictly ask user to select each target to proceed.
21
- - `muggle-remote-project-list`
22
- - `muggle-remote-use-case-list`
23
- - `muggle-remote-test-case-list-by-use-case`
24
-
25
- 3. **Resolve local URL**
26
- - Use the URL provided by the user.
27
- - If missing, ask explicitly (do not guess).
28
- - Inform user the local URL does not affect the project's remote test.
29
-
30
- 4. **Check script availability**
31
- - `muggle-remote-test-script-list` filtered by testCaseId
32
- - If script exists, recommend replay unless user-flow changes suggest regeneration.
33
-
34
- 5. **Execute**
35
- - Replay path:
36
- - `muggle-remote-test-script-get`
37
- - `muggle-local-execute-replay`
38
- - Generation path:
39
- - `muggle-remote-test-case-get`
40
- - `muggle-local-execute-test-generation`
41
-
42
- 6. **Approval requirement**
43
- - Before execution, get explicit user approval for launching Electron app.
44
- - Only then set `approveElectronAppLaunch: true`.
45
-
46
- 7. **Publish local generation result to MuggleTest cloud records**
47
- - Use `muggle-local-publish-test-script` after successful generation so user can see the full job details.
48
- - Return the remote URL for user to view the result and script details.
49
-
50
- 8. **Report results**
51
- - `muggle-local-run-result-get` with returned runId.
52
- - Report:
53
- - status
54
- - duration
55
- - pass/fail summary
56
- - steps summary
57
- - artifacts path
58
- - script detail view URL
15
+ ### 1. Auth
16
+
17
+ - `muggle-remote-auth-status`
18
+ - If needed: `muggle-remote-auth-login` + `muggle-remote-auth-poll`
19
+
20
+ ### 2. Select project, use case, and test case
21
+
22
+ - Explicitly ask user to select each target to proceed.
23
+ - `muggle-remote-project-list`
24
+ - `muggle-remote-use-case-list`
25
+ - `muggle-remote-test-case-list-by-use-case`
26
+
27
+ ### 3. Resolve local URL
28
+
29
+ - Use the URL provided by the user.
30
+ - If missing, ask explicitly (do not guess).
31
+ - Inform user the local URL does not affect the project's remote test.
32
+
33
+ ### 4. Check for existing scripts and ask user to choose
34
+
35
+ Check BOTH cloud and local scripts to determine what's available:
36
+
37
+ 1. **Check cloud scripts:** `muggle-remote-test-script-list` filtered by projectId
38
+ 2. **Check local scripts:** `muggle-local-test-script-list` filtered by projectId
39
+
40
+ **Decision logic:**
41
+
42
+ | Cloud Script | Local Script (status: published/generated) | Action |
43
+ |--------------|---------------------------------------------|--------|
44
+ | Exists + ACTIVE | Exists | Ask user: "Replay existing script" or "Regenerate from scratch"? |
45
+ | Exists + ACTIVE | Not found | Sync from cloud first, then ask user |
46
+ | Not found | Exists | Ask user: "Replay local script" or "Regenerate"? |
47
+ | Not found | Not found | Default to generation (no need to ask) |
48
+
49
+ **When asking user, show:**
50
+ - Script name and ID
51
+ - When it was created/updated
52
+ - Number of steps
53
+ - Last run status if available
54
+
55
+ ### 5. Prepare for execution
56
+
57
+ **For Replay:**
58
+
59
+ Local scripts contain the complete `actionScript` with element labels required for replay. Remote scripts only contain metadata.
60
+
61
+ 1. Use `muggle-local-test-script-get` with `testScriptId` to fetch the FULL script including actionScript
62
+ 2. The returned script includes all steps with `operation.label` paths needed for element location
63
+ 3. Pass this complete script to `muggle-local-execute-replay`
64
+
65
+ **IMPORTANT:** Do NOT manually construct or simplify the actionScript. The electron app requires the complete script with all `label` paths intact to locate page elements during replay.
66
+
67
+ **For Generation:**
68
+
69
+ 1. `muggle-remote-test-case-get` to fetch test case details
70
+ 2. `muggle-local-execute-test-generation` with the test case
71
+
72
+ ### 6. Approval requirement
73
+
74
+ - Before execution, get explicit user approval for launching Electron app.
75
+ - Show what will be executed (replay vs generation, test case name, URL).
76
+ - Only then set `approveElectronAppLaunch: true`.
77
+
78
+ ### 7. Execute
79
+
80
+ **Replay:**
81
+ ```
82
+ muggle-local-execute-replay with:
83
+ - testScript: (full script from muggle-local-test-script-get)
84
+ - localUrl: user-provided localhost URL
85
+ - approveElectronAppLaunch: true
86
+ - showUi: true (optional, lets user watch)
87
+ ```
88
+
89
+ **Generation:**
90
+ ```
91
+ muggle-local-execute-test-generation with:
92
+ - testCase: (from muggle-remote-test-case-get)
93
+ - localUrl: user-provided localhost URL
94
+ - approveElectronAppLaunch: true
95
+ - showUi: true (optional)
96
+ ```
97
+
98
+ ### 8. Publish generation results (generation only)
99
+
100
+ - Use `muggle-local-publish-test-script` after successful generation.
101
+ - This uploads the script to cloud so it can be replayed later.
102
+ - Return the remote URL for user to view the result.
103
+
104
+ ### 9. Report results
105
+
106
+ - `muggle-local-run-result-get` with returned runId.
107
+ - Report:
108
+ - status (passed/failed)
109
+ - duration
110
+ - pass/fail summary
111
+ - steps summary (which steps passed/failed)
112
+ - artifacts path (screenshots location)
113
+ - script detail view URL
59
114
 
60
115
  ## Guardrails
61
116
 
62
117
  - Do not silently skip auth.
63
- - Do not silently skip test execution when no script exists; generate one.
118
+ - Do not silently skip asking user when a replayable script exists.
64
119
  - Do not launch Electron without explicit approval.
65
120
  - Do not hide failing run details; include error and artifacts path.
121
+ - Do not simplify or reconstruct actionScript for replay; use the complete script from `muggle-local-test-script-get`.
122
+ - Always check local scripts before defaulting to generation.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@muggleai/works",
3
3
  "mcpName": "io.github.multiplex-ai/muggle",
4
- "version": "4.0.1",
4
+ "version": "4.0.3",
5
5
  "description": "Ship quality products with AI-powered QA that validates your app's user experience — from Claude Code and Cursor to PR.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "muggle",
3
3
  "description": "Run real-browser QA 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.0.1",
4
+ "version": "4.0.3",
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 QA that validates your app's user experience — from Claude Code and Cursor to PR.",
5
- "version": "4.0.1",
5
+ "version": "4.0.3",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: muggle-test-feature-local
3
- description: Test a feature's user experience on localhost. Use when user types muggle test-feature-local, test my app, run QA, or validate UI changes.
3
+ description: Run a real-browser QA test against localhost to verify a feature works correctly — signup flows, checkout, form validation, UI interactions, or any user-facing behavior. Launches a browser that executes test steps and captures screenshots. Use this skill whenever the user asks to test, QA, validate, or verify their web app, UI changes, user flows, or frontend behavior on localhost or a dev server — even if they don't mention 'muggle' or 'QA' explicitly.
4
4
  ---
5
5
 
6
6
  # Muggle Test Feature Local
@@ -12,54 +12,111 @@ Run end-to-end feature testing from UI against a local URL:
12
12
 
13
13
  ## Workflow
14
14
 
15
- 1. **Auth**
16
- - `muggle-remote-auth-status`
17
- - If needed: `muggle-remote-auth-login` + `muggle-remote-auth-poll`
18
-
19
- 2. **Select or create the project -> use case -> test case to use**
20
- - Explictly ask user to select each target to proceed.
21
- - `muggle-remote-project-list`
22
- - `muggle-remote-use-case-list`
23
- - `muggle-remote-test-case-list-by-use-case`
24
-
25
- 3. **Resolve local URL**
26
- - Use the URL provided by the user.
27
- - If missing, ask explicitly (do not guess).
28
- - Inform user the local URL does not affect the project's remote test.
29
-
30
- 4. **Check script availability**
31
- - `muggle-remote-test-script-list` filtered by testCaseId
32
- - If script exists, recommend replay unless user-flow changes suggest regeneration.
33
-
34
- 5. **Execute**
35
- - Replay path:
36
- - `muggle-remote-test-script-get`
37
- - `muggle-local-execute-replay`
38
- - Generation path:
39
- - `muggle-remote-test-case-get`
40
- - `muggle-local-execute-test-generation`
41
-
42
- 6. **Approval requirement**
43
- - Before execution, get explicit user approval for launching Electron app.
44
- - Only then set `approveElectronAppLaunch: true`.
45
-
46
- 7. **Publish local generation result to MuggleTest cloud records**
47
- - Use `muggle-local-publish-test-script` after successful generation so user can see the full job details.
48
- - Return the remote URL for user to view the result and script details.
49
-
50
- 8. **Report results**
51
- - `muggle-local-run-result-get` with returned runId.
52
- - Report:
53
- - status
54
- - duration
55
- - pass/fail summary
56
- - steps summary
57
- - artifacts path
58
- - script detail view URL
15
+ ### 1. Auth
16
+
17
+ - `muggle-remote-auth-status`
18
+ - If needed: `muggle-remote-auth-login` + `muggle-remote-auth-poll`
19
+
20
+ ### 2. Select project, use case, and test case
21
+
22
+ - Explicitly ask user to select each target to proceed.
23
+ - `muggle-remote-project-list`
24
+ - `muggle-remote-use-case-list`
25
+ - `muggle-remote-test-case-list-by-use-case`
26
+
27
+ ### 3. Resolve local URL
28
+
29
+ - Use the URL provided by the user.
30
+ - If missing, ask explicitly (do not guess).
31
+ - Inform user the local URL does not affect the project's remote test.
32
+
33
+ ### 4. Check for existing scripts and ask user to choose
34
+
35
+ Check BOTH cloud and local scripts to determine what's available:
36
+
37
+ 1. **Check cloud scripts:** `muggle-remote-test-script-list` filtered by projectId
38
+ 2. **Check local scripts:** `muggle-local-test-script-list` filtered by projectId
39
+
40
+ **Decision logic:**
41
+
42
+ | Cloud Script | Local Script (status: published/generated) | Action |
43
+ |--------------|---------------------------------------------|--------|
44
+ | Exists + ACTIVE | Exists | Ask user: "Replay existing script" or "Regenerate from scratch"? |
45
+ | Exists + ACTIVE | Not found | Sync from cloud first, then ask user |
46
+ | Not found | Exists | Ask user: "Replay local script" or "Regenerate"? |
47
+ | Not found | Not found | Default to generation (no need to ask) |
48
+
49
+ **When asking user, show:**
50
+ - Script name and ID
51
+ - When it was created/updated
52
+ - Number of steps
53
+ - Last run status if available
54
+
55
+ ### 5. Prepare for execution
56
+
57
+ **For Replay:**
58
+
59
+ Local scripts contain the complete `actionScript` with element labels required for replay. Remote scripts only contain metadata.
60
+
61
+ 1. Use `muggle-local-test-script-get` with `testScriptId` to fetch the FULL script including actionScript
62
+ 2. The returned script includes all steps with `operation.label` paths needed for element location
63
+ 3. Pass this complete script to `muggle-local-execute-replay`
64
+
65
+ **IMPORTANT:** Do NOT manually construct or simplify the actionScript. The electron app requires the complete script with all `label` paths intact to locate page elements during replay.
66
+
67
+ **For Generation:**
68
+
69
+ 1. `muggle-remote-test-case-get` to fetch test case details
70
+ 2. `muggle-local-execute-test-generation` with the test case
71
+
72
+ ### 6. Approval requirement
73
+
74
+ - Before execution, get explicit user approval for launching Electron app.
75
+ - Show what will be executed (replay vs generation, test case name, URL).
76
+ - Only then set `approveElectronAppLaunch: true`.
77
+
78
+ ### 7. Execute
79
+
80
+ **Replay:**
81
+ ```
82
+ muggle-local-execute-replay with:
83
+ - testScript: (full script from muggle-local-test-script-get)
84
+ - localUrl: user-provided localhost URL
85
+ - approveElectronAppLaunch: true
86
+ - showUi: true (optional, lets user watch)
87
+ ```
88
+
89
+ **Generation:**
90
+ ```
91
+ muggle-local-execute-test-generation with:
92
+ - testCase: (from muggle-remote-test-case-get)
93
+ - localUrl: user-provided localhost URL
94
+ - approveElectronAppLaunch: true
95
+ - showUi: true (optional)
96
+ ```
97
+
98
+ ### 8. Publish generation results (generation only)
99
+
100
+ - Use `muggle-local-publish-test-script` after successful generation.
101
+ - This uploads the script to cloud so it can be replayed later.
102
+ - Return the remote URL for user to view the result.
103
+
104
+ ### 9. Report results
105
+
106
+ - `muggle-local-run-result-get` with returned runId.
107
+ - Report:
108
+ - status (passed/failed)
109
+ - duration
110
+ - pass/fail summary
111
+ - steps summary (which steps passed/failed)
112
+ - artifacts path (screenshots location)
113
+ - script detail view URL
59
114
 
60
115
  ## Guardrails
61
116
 
62
117
  - Do not silently skip auth.
63
- - Do not silently skip test execution when no script exists; generate one.
118
+ - Do not silently skip asking user when a replayable script exists.
64
119
  - Do not launch Electron without explicit approval.
65
120
  - Do not hide failing run details; include error and artifacts path.
121
+ - Do not simplify or reconstruct actionScript for replay; use the complete script from `muggle-local-test-script-get`.
122
+ - Always check local scripts before defaulting to generation.