@muggleai/works 4.6.0 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-2FVSZ5LQ.js +2176 -0
- package/dist/{chunk-TP4T4T2Z.js → chunk-HDEZDEM6.js} +721 -2747
- package/dist/cli.js +2 -1
- package/dist/index.js +2 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/skills/do/open-prs.md +2 -1
- package/dist/plugin/skills/muggle-pr-visual-walkthrough/SKILL.md +12 -2
- package/dist/plugin/skills/muggle-test/SKILL.md +3 -0
- package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +3 -0
- package/dist/release-manifest.json +4 -4
- package/dist/src-TX2KXI26.js +1 -0
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/skills/do/open-prs.md +2 -1
- package/plugin/skills/muggle-pr-visual-walkthrough/SKILL.md +12 -2
- package/plugin/skills/muggle-test/SKILL.md +3 -0
- package/plugin/skills/muggle-test-feature-local/SKILL.md +3 -0
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import {
|
|
3
|
-
import * as
|
|
4
|
-
import {
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import * as fs5 from 'fs';
|
|
4
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
5
5
|
import * as path2 from 'path';
|
|
6
|
-
import { join, dirname
|
|
7
|
-
import
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import * as os3 from 'os';
|
|
8
|
+
import { platform } from 'os';
|
|
8
9
|
import winston from 'winston';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { spawn, exec } from 'child_process';
|
|
9
12
|
import axios, { AxiosError } from 'axios';
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import { randomUUID } from 'crypto';
|
|
13
|
-
import * as fs5 from 'fs/promises';
|
|
14
|
-
import { z, ZodError } from 'zod';
|
|
15
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
16
|
-
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
17
|
-
import { v4 } from 'uuid';
|
|
18
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
19
|
-
import { Command } from 'commander';
|
|
20
|
-
import { pipeline } from 'stream/promises';
|
|
13
|
+
import * as fs7 from 'fs/promises';
|
|
14
|
+
import { z } from 'zod';
|
|
21
15
|
|
|
22
16
|
var __defProp = Object.defineProperty;
|
|
23
17
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
@@ -30,6 +24,60 @@ var __export = (target, all) => {
|
|
|
30
24
|
for (var name in all)
|
|
31
25
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
32
26
|
};
|
|
27
|
+
|
|
28
|
+
// packages/mcps/src/index.ts
|
|
29
|
+
var src_exports = {};
|
|
30
|
+
__export(src_exports, {
|
|
31
|
+
buildElectronAppChecksumsUrl: () => buildElectronAppChecksumsUrl,
|
|
32
|
+
buildElectronAppReleaseAssetUrl: () => buildElectronAppReleaseAssetUrl,
|
|
33
|
+
buildElectronAppReleaseTag: () => buildElectronAppReleaseTag,
|
|
34
|
+
calculateFileChecksum: () => calculateFileChecksum,
|
|
35
|
+
createApiKeyWithToken: () => createApiKeyWithToken,
|
|
36
|
+
createChildLogger: () => createChildLogger,
|
|
37
|
+
deleteApiKeyData: () => deleteApiKeyData,
|
|
38
|
+
deleteCredentials: () => deleteCredentials,
|
|
39
|
+
e2e: () => e2e_exports2,
|
|
40
|
+
getApiKey: () => getApiKey,
|
|
41
|
+
getApiKeyFilePath: () => getApiKeyFilePath,
|
|
42
|
+
getAuthService: () => getAuthService,
|
|
43
|
+
getBundledElectronAppVersion: () => getBundledElectronAppVersion,
|
|
44
|
+
getCallerCredentials: () => getCallerCredentials,
|
|
45
|
+
getCallerCredentialsAsync: () => getCallerCredentialsAsync,
|
|
46
|
+
getChecksumForPlatform: () => getChecksumForPlatform,
|
|
47
|
+
getConfig: () => getConfig,
|
|
48
|
+
getCredentialsFilePath: () => getCredentialsFilePath,
|
|
49
|
+
getDataDir: () => getDataDir2,
|
|
50
|
+
getDownloadBaseUrl: () => getDownloadBaseUrl,
|
|
51
|
+
getElectronAppChecksums: () => getElectronAppChecksums,
|
|
52
|
+
getElectronAppDir: () => getElectronAppDir,
|
|
53
|
+
getElectronAppVersion: () => getElectronAppVersion,
|
|
54
|
+
getElectronAppVersionSource: () => getElectronAppVersionSource,
|
|
55
|
+
getLocalQaTools: () => getLocalQaTools,
|
|
56
|
+
getLogger: () => getLogger,
|
|
57
|
+
getPlatformKey: () => getPlatformKey,
|
|
58
|
+
getQaTools: () => getQaTools,
|
|
59
|
+
getValidApiKeyData: () => getValidApiKeyData,
|
|
60
|
+
getValidCredentials: () => getValidCredentials,
|
|
61
|
+
hasApiKey: () => hasApiKey,
|
|
62
|
+
isElectronAppInstalled: () => isElectronAppInstalled,
|
|
63
|
+
loadApiKeyData: () => loadApiKeyData,
|
|
64
|
+
loadCredentials: () => loadCredentials,
|
|
65
|
+
localQa: () => local_exports2,
|
|
66
|
+
mcp: () => mcp_exports,
|
|
67
|
+
openBrowserUrl: () => openBrowserUrl,
|
|
68
|
+
performLogin: () => performLogin,
|
|
69
|
+
performLogout: () => performLogout,
|
|
70
|
+
pollDeviceCode: () => pollDeviceCode,
|
|
71
|
+
qa: () => e2e_exports2,
|
|
72
|
+
resetConfig: () => resetConfig,
|
|
73
|
+
resetLogger: () => resetLogger,
|
|
74
|
+
saveApiKey: () => saveApiKey,
|
|
75
|
+
saveApiKeyData: () => saveApiKeyData,
|
|
76
|
+
saveCredentials: () => saveCredentials,
|
|
77
|
+
startDeviceCodeFlow: () => startDeviceCodeFlow,
|
|
78
|
+
toolRequiresAuth: () => toolRequiresAuth,
|
|
79
|
+
verifyFileChecksum: () => verifyFileChecksum
|
|
80
|
+
});
|
|
33
81
|
var DATA_DIR_NAME = ".muggle-ai";
|
|
34
82
|
function getDataDir() {
|
|
35
83
|
return path2.join(os3.homedir(), DATA_DIR_NAME);
|
|
@@ -116,7 +164,7 @@ function getMuggleConfig() {
|
|
|
116
164
|
const packageJsonPath = path2.join(packageRoot, "package.json");
|
|
117
165
|
let packageJson;
|
|
118
166
|
try {
|
|
119
|
-
const content =
|
|
167
|
+
const content = fs5.readFileSync(packageJsonPath, "utf-8");
|
|
120
168
|
packageJson = JSON.parse(content);
|
|
121
169
|
} catch (error) {
|
|
122
170
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -191,7 +239,7 @@ function getDownloadedElectronAppPath() {
|
|
|
191
239
|
default:
|
|
192
240
|
return null;
|
|
193
241
|
}
|
|
194
|
-
if (
|
|
242
|
+
if (fs5.existsSync(binaryPath)) {
|
|
195
243
|
return binaryPath;
|
|
196
244
|
}
|
|
197
245
|
return null;
|
|
@@ -213,14 +261,14 @@ function getSystemElectronAppPath() {
|
|
|
213
261
|
default:
|
|
214
262
|
return null;
|
|
215
263
|
}
|
|
216
|
-
if (
|
|
264
|
+
if (fs5.existsSync(binaryPath)) {
|
|
217
265
|
return binaryPath;
|
|
218
266
|
}
|
|
219
267
|
return null;
|
|
220
268
|
}
|
|
221
269
|
function resolveElectronAppPathOrNull() {
|
|
222
270
|
const customPath = process.env.ELECTRON_APP_PATH;
|
|
223
|
-
if (customPath &&
|
|
271
|
+
if (customPath && fs5.existsSync(customPath)) {
|
|
224
272
|
return customPath;
|
|
225
273
|
}
|
|
226
274
|
const downloadedPath = getDownloadedElectronAppPath();
|
|
@@ -235,12 +283,12 @@ function resolveElectronAppPathOrNull() {
|
|
|
235
283
|
}
|
|
236
284
|
function resolveWebServicePath() {
|
|
237
285
|
const customPath = process.env.WEB_SERVICE_PATH;
|
|
238
|
-
if (customPath &&
|
|
286
|
+
if (customPath && fs5.existsSync(customPath)) {
|
|
239
287
|
return customPath;
|
|
240
288
|
}
|
|
241
289
|
const packageRoot = getPackageRoot();
|
|
242
290
|
const siblingPath = path2.resolve(packageRoot, "..", "web-service", "dist", "src", "index.js");
|
|
243
|
-
if (
|
|
291
|
+
if (fs5.existsSync(siblingPath)) {
|
|
244
292
|
return siblingPath;
|
|
245
293
|
}
|
|
246
294
|
return null;
|
|
@@ -369,9 +417,9 @@ function getElectronAppVersion() {
|
|
|
369
417
|
return envVersion;
|
|
370
418
|
}
|
|
371
419
|
const overridePath = path2.join(getDataDir2(), VERSION_OVERRIDE_FILE);
|
|
372
|
-
if (
|
|
420
|
+
if (fs5.existsSync(overridePath)) {
|
|
373
421
|
try {
|
|
374
|
-
const content = JSON.parse(
|
|
422
|
+
const content = JSON.parse(fs5.readFileSync(overridePath, "utf-8"));
|
|
375
423
|
if (content.version && typeof content.version === "string") {
|
|
376
424
|
return content.version;
|
|
377
425
|
}
|
|
@@ -386,9 +434,9 @@ function getElectronAppVersionSource() {
|
|
|
386
434
|
return "env";
|
|
387
435
|
}
|
|
388
436
|
const overridePath = path2.join(getDataDir2(), VERSION_OVERRIDE_FILE);
|
|
389
|
-
if (
|
|
437
|
+
if (fs5.existsSync(overridePath)) {
|
|
390
438
|
try {
|
|
391
|
-
const content = JSON.parse(
|
|
439
|
+
const content = JSON.parse(fs5.readFileSync(overridePath, "utf-8"));
|
|
392
440
|
if (content.version && typeof content.version === "string") {
|
|
393
441
|
return "override";
|
|
394
442
|
}
|
|
@@ -422,6 +470,8 @@ function getElectronAppDir(version) {
|
|
|
422
470
|
const resolvedVersion = version ?? getElectronAppVersion();
|
|
423
471
|
return path2.join(getDataDir2(), ELECTRON_APP_DIR, resolvedVersion);
|
|
424
472
|
}
|
|
473
|
+
|
|
474
|
+
// packages/mcps/src/shared/logger.ts
|
|
425
475
|
var loggerInstance = null;
|
|
426
476
|
function createLogger() {
|
|
427
477
|
const config = getConfig();
|
|
@@ -451,109 +501,101 @@ function getLogger() {
|
|
|
451
501
|
return loggerInstance;
|
|
452
502
|
}
|
|
453
503
|
function createChildLogger(correlationId) {
|
|
454
|
-
const
|
|
455
|
-
return
|
|
504
|
+
const logger6 = getLogger();
|
|
505
|
+
return logger6.child({ correlationId });
|
|
456
506
|
}
|
|
457
507
|
function resetLogger() {
|
|
458
508
|
loggerInstance = null;
|
|
459
509
|
}
|
|
460
510
|
|
|
461
|
-
// packages/mcps/src/
|
|
462
|
-
var e2e_exports2 = {};
|
|
463
|
-
__export(e2e_exports2, {
|
|
464
|
-
ActionScriptGetInputSchema: () => ActionScriptGetInputSchema,
|
|
465
|
-
ApiKeyCreateInputSchema: () => ApiKeyCreateInputSchema,
|
|
466
|
-
ApiKeyGetInputSchema: () => ApiKeyGetInputSchema,
|
|
467
|
-
ApiKeyListInputSchema: () => ApiKeyListInputSchema,
|
|
468
|
-
ApiKeyRecordIdSchema: () => ApiKeyRecordIdSchema,
|
|
469
|
-
ApiKeyRevokeInputSchema: () => ApiKeyRevokeInputSchema,
|
|
470
|
-
AuthLoginInputSchema: () => AuthLoginInputSchema,
|
|
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,
|
|
480
|
-
EmptyInputSchema: () => EmptyInputSchema,
|
|
481
|
-
GatewayError: () => GatewayError,
|
|
482
|
-
IdSchema: () => IdSchema,
|
|
483
|
-
LocalExecutionContextInputSchema: () => LocalExecutionContextInputSchema,
|
|
484
|
-
LocalRunUploadInputSchema: () => LocalRunUploadInputSchema,
|
|
485
|
-
McpErrorCode: () => McpErrorCode,
|
|
486
|
-
MuggleEntityIdSchema: () => MuggleEntityIdSchema,
|
|
487
|
-
PaginationInputSchema: () => PaginationInputSchema,
|
|
488
|
-
PrdFileDeleteInputSchema: () => PrdFileDeleteInputSchema,
|
|
489
|
-
PrdFileListInputSchema: () => PrdFileListInputSchema,
|
|
490
|
-
PrdFileProcessLatestRunInputSchema: () => PrdFileProcessLatestRunInputSchema,
|
|
491
|
-
PrdFileProcessStartInputSchema: () => PrdFileProcessStartInputSchema,
|
|
492
|
-
PrdFileUploadInputSchema: () => PrdFileUploadInputSchema,
|
|
493
|
-
ProjectCreateInputSchema: () => ProjectCreateInputSchema,
|
|
494
|
-
ProjectDeleteInputSchema: () => ProjectDeleteInputSchema,
|
|
495
|
-
ProjectGetInputSchema: () => ProjectGetInputSchema,
|
|
496
|
-
ProjectListInputSchema: () => ProjectListInputSchema,
|
|
497
|
-
ProjectTestResultsSummaryInputSchema: () => ProjectTestResultsSummaryInputSchema,
|
|
498
|
-
ProjectTestRunsSummaryInputSchema: () => ProjectTestRunsSummaryInputSchema,
|
|
499
|
-
ProjectTestScriptsSummaryInputSchema: () => ProjectTestScriptsSummaryInputSchema,
|
|
500
|
-
ProjectUpdateInputSchema: () => ProjectUpdateInputSchema,
|
|
501
|
-
PromptServiceClient: () => PromptServiceClient,
|
|
502
|
-
RecommendCicdSetupInputSchema: () => RecommendCicdSetupInputSchema,
|
|
503
|
-
RecommendScheduleInputSchema: () => RecommendScheduleInputSchema,
|
|
504
|
-
ReportCostQueryInputSchema: () => ReportCostQueryInputSchema,
|
|
505
|
-
ReportFinalGenerateInputSchema: () => ReportFinalGenerateInputSchema,
|
|
506
|
-
ReportPreferencesUpsertInputSchema: () => ReportPreferencesUpsertInputSchema,
|
|
507
|
-
ReportStatsSummaryInputSchema: () => ReportStatsSummaryInputSchema,
|
|
508
|
-
RunBatchIdSchema: () => RunBatchIdSchema,
|
|
509
|
-
SecretCreateInputSchema: () => SecretCreateInputSchema,
|
|
510
|
-
SecretDeleteInputSchema: () => SecretDeleteInputSchema,
|
|
511
|
-
SecretGetInputSchema: () => SecretGetInputSchema,
|
|
512
|
-
SecretListInputSchema: () => SecretListInputSchema,
|
|
513
|
-
SecretUpdateInputSchema: () => SecretUpdateInputSchema,
|
|
514
|
-
StripePaymentMethodIdSchema: () => StripePaymentMethodIdSchema,
|
|
515
|
-
TestCaseCreateInputSchema: () => TestCaseCreateInputSchema,
|
|
516
|
-
TestCaseGenerateFromPromptInputSchema: () => TestCaseGenerateFromPromptInputSchema,
|
|
517
|
-
TestCaseGetInputSchema: () => TestCaseGetInputSchema,
|
|
518
|
-
TestCaseListByUseCaseInputSchema: () => TestCaseListByUseCaseInputSchema,
|
|
519
|
-
TestCaseListInputSchema: () => TestCaseListInputSchema,
|
|
520
|
-
TestScriptGetInputSchema: () => TestScriptGetInputSchema,
|
|
521
|
-
TestScriptListInputSchema: () => TestScriptListInputSchema,
|
|
522
|
-
TokenPackageIdSchema: () => TokenPackageIdSchema,
|
|
523
|
-
TokenUsageFilterTypeSchema: () => TokenUsageFilterTypeSchema,
|
|
524
|
-
UseCaseCandidatesApproveInputSchema: () => UseCaseCandidatesApproveInputSchema,
|
|
525
|
-
UseCaseCreateFromPromptsInputSchema: () => UseCaseCreateFromPromptsInputSchema,
|
|
526
|
-
UseCaseCreateInputSchema: () => UseCaseCreateInputSchema,
|
|
527
|
-
UseCaseDiscoveryMemoryGetInputSchema: () => UseCaseDiscoveryMemoryGetInputSchema,
|
|
528
|
-
UseCaseGetInputSchema: () => UseCaseGetInputSchema,
|
|
529
|
-
UseCaseListInputSchema: () => UseCaseListInputSchema,
|
|
530
|
-
UseCasePromptPreviewInputSchema: () => UseCasePromptPreviewInputSchema,
|
|
531
|
-
UseCaseUpdateFromPromptInputSchema: () => UseCaseUpdateFromPromptInputSchema,
|
|
532
|
-
WalletAutoTopUpSetPaymentMethodInputSchema: () => WalletAutoTopUpSetPaymentMethodInputSchema,
|
|
533
|
-
WalletAutoTopUpUpdateInputSchema: () => WalletAutoTopUpUpdateInputSchema,
|
|
534
|
-
WalletPaymentMethodCreateSetupSessionInputSchema: () => WalletPaymentMethodCreateSetupSessionInputSchema,
|
|
535
|
-
WalletPaymentMethodListInputSchema: () => WalletPaymentMethodListInputSchema,
|
|
536
|
-
WalletTopUpInputSchema: () => WalletTopUpInputSchema,
|
|
537
|
-
WorkflowCancelRunInputSchema: () => WorkflowCancelRunInputSchema,
|
|
538
|
-
WorkflowCancelRuntimeInputSchema: () => WorkflowCancelRuntimeInputSchema,
|
|
539
|
-
WorkflowGetLatestRunInputSchema: () => WorkflowGetLatestRunInputSchema,
|
|
540
|
-
WorkflowGetLatestScriptGenByTestCaseInputSchema: () => WorkflowGetLatestScriptGenByTestCaseInputSchema,
|
|
541
|
-
WorkflowGetReplayBulkBatchSummaryInputSchema: () => WorkflowGetReplayBulkBatchSummaryInputSchema,
|
|
542
|
-
WorkflowListRuntimesInputSchema: () => WorkflowListRuntimesInputSchema,
|
|
543
|
-
WorkflowMemoryParamsSchema: () => WorkflowMemoryParamsSchema,
|
|
544
|
-
WorkflowParamsSchema: () => WorkflowParamsSchema,
|
|
545
|
-
WorkflowStartTestCaseDetectionInputSchema: () => WorkflowStartTestCaseDetectionInputSchema,
|
|
546
|
-
WorkflowStartTestScriptGenerationInputSchema: () => WorkflowStartTestScriptGenerationInputSchema,
|
|
547
|
-
WorkflowStartTestScriptReplayBulkInputSchema: () => WorkflowStartTestScriptReplayBulkInputSchema,
|
|
548
|
-
WorkflowStartTestScriptReplayInputSchema: () => WorkflowStartTestScriptReplayInputSchema,
|
|
549
|
-
WorkflowStartWebsiteScanInputSchema: () => WorkflowStartWebsiteScanInputSchema,
|
|
550
|
-
allQaToolDefinitions: () => allQaToolDefinitions,
|
|
551
|
-
executeQaTool: () => executeQaTool,
|
|
552
|
-
getPromptServiceClient: () => getPromptServiceClient,
|
|
553
|
-
getQaToolByName: () => getQaToolByName,
|
|
554
|
-
getQaTools: () => getQaTools
|
|
555
|
-
});
|
|
511
|
+
// packages/mcps/src/shared/checksum.ts
|
|
556
512
|
var logger = getLogger();
|
|
513
|
+
function getPlatformKey() {
|
|
514
|
+
const os4 = platform();
|
|
515
|
+
const arch2 = process.arch;
|
|
516
|
+
switch (os4) {
|
|
517
|
+
case "darwin":
|
|
518
|
+
return arch2 === "arm64" ? "darwin-arm64" : "darwin-x64";
|
|
519
|
+
case "win32":
|
|
520
|
+
return "win32-x64";
|
|
521
|
+
case "linux":
|
|
522
|
+
return "linux-x64";
|
|
523
|
+
default:
|
|
524
|
+
throw new Error(`Unsupported platform: ${os4}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async function calculateFileChecksum(filePath) {
|
|
528
|
+
return new Promise((resolve3, reject) => {
|
|
529
|
+
const hash = crypto.createHash("sha256");
|
|
530
|
+
const stream = fs5.createReadStream(filePath);
|
|
531
|
+
stream.on("data", (data) => {
|
|
532
|
+
hash.update(data);
|
|
533
|
+
});
|
|
534
|
+
stream.on("end", () => {
|
|
535
|
+
resolve3(hash.digest("hex"));
|
|
536
|
+
});
|
|
537
|
+
stream.on("error", (error) => {
|
|
538
|
+
reject(error);
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
async function verifyFileChecksum(filePath, expectedChecksum) {
|
|
543
|
+
if (!expectedChecksum || expectedChecksum.trim() === "") {
|
|
544
|
+
logger.warn("Checksum verification skipped - no checksum provided", {
|
|
545
|
+
file: path2.basename(filePath)
|
|
546
|
+
});
|
|
547
|
+
return {
|
|
548
|
+
valid: true,
|
|
549
|
+
expected: "",
|
|
550
|
+
actual: "",
|
|
551
|
+
error: "Checksum verification skipped - no checksum configured"
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
const actualChecksum = await calculateFileChecksum(filePath);
|
|
556
|
+
const normalizedExpected = expectedChecksum.toLowerCase().trim();
|
|
557
|
+
const normalizedActual = actualChecksum.toLowerCase();
|
|
558
|
+
const valid = normalizedExpected === normalizedActual;
|
|
559
|
+
if (!valid) {
|
|
560
|
+
logger.error("Checksum verification failed", {
|
|
561
|
+
file: path2.basename(filePath),
|
|
562
|
+
expected: normalizedExpected,
|
|
563
|
+
actual: normalizedActual
|
|
564
|
+
});
|
|
565
|
+
} else {
|
|
566
|
+
logger.info("Checksum verified successfully", {
|
|
567
|
+
file: path2.basename(filePath),
|
|
568
|
+
checksum: normalizedActual
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
return {
|
|
572
|
+
valid,
|
|
573
|
+
expected: normalizedExpected,
|
|
574
|
+
actual: normalizedActual,
|
|
575
|
+
error: valid ? void 0 : "Checksum mismatch - file may be corrupted or tampered with"
|
|
576
|
+
};
|
|
577
|
+
} catch (error) {
|
|
578
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
579
|
+
logger.error("Checksum calculation failed", {
|
|
580
|
+
file: path2.basename(filePath),
|
|
581
|
+
error: errorMessage
|
|
582
|
+
});
|
|
583
|
+
return {
|
|
584
|
+
valid: false,
|
|
585
|
+
expected: expectedChecksum,
|
|
586
|
+
actual: "",
|
|
587
|
+
error: `Failed to calculate checksum: ${errorMessage}`
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function getChecksumForPlatform(checksums) {
|
|
592
|
+
if (!checksums) {
|
|
593
|
+
return "";
|
|
594
|
+
}
|
|
595
|
+
const platformKey = getPlatformKey();
|
|
596
|
+
return checksums[platformKey] || "";
|
|
597
|
+
}
|
|
598
|
+
var logger2 = getLogger();
|
|
557
599
|
function getOpenCommand(url) {
|
|
558
600
|
const platformName = platform();
|
|
559
601
|
switch (platformName) {
|
|
@@ -568,59 +610,160 @@ function getOpenCommand(url) {
|
|
|
568
610
|
}
|
|
569
611
|
}
|
|
570
612
|
async function openBrowserUrl(options) {
|
|
571
|
-
return new Promise((
|
|
613
|
+
return new Promise((resolve3) => {
|
|
572
614
|
try {
|
|
573
615
|
const command = getOpenCommand(options.url);
|
|
574
|
-
|
|
616
|
+
logger2.debug("[Browser] Opening URL", { url: options.url, command });
|
|
575
617
|
exec(command, (error) => {
|
|
576
618
|
if (error) {
|
|
577
|
-
|
|
619
|
+
logger2.warn("[Browser] Failed to open URL", {
|
|
578
620
|
url: options.url,
|
|
579
621
|
error: error.message
|
|
580
622
|
});
|
|
581
|
-
|
|
623
|
+
resolve3({
|
|
582
624
|
opened: false,
|
|
583
625
|
error: error.message
|
|
584
626
|
});
|
|
585
627
|
} else {
|
|
586
|
-
|
|
587
|
-
|
|
628
|
+
logger2.info("[Browser] URL opened successfully", { url: options.url });
|
|
629
|
+
resolve3({ opened: true });
|
|
588
630
|
}
|
|
589
631
|
});
|
|
590
632
|
} catch (error) {
|
|
591
633
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
592
|
-
|
|
634
|
+
logger2.warn("[Browser] Failed to open URL", {
|
|
593
635
|
url: options.url,
|
|
594
636
|
error: errorMessage
|
|
595
637
|
});
|
|
596
|
-
|
|
638
|
+
resolve3({
|
|
597
639
|
opened: false,
|
|
598
640
|
error: errorMessage
|
|
599
641
|
});
|
|
600
642
|
}
|
|
601
643
|
});
|
|
602
644
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
645
|
+
var API_KEY_FILE2 = "api-key.json";
|
|
646
|
+
function getApiKeyFilePath() {
|
|
647
|
+
return path2.join(getDataDir(), API_KEY_FILE2);
|
|
648
|
+
}
|
|
649
|
+
function ensureDataDir() {
|
|
650
|
+
const dataDir = getDataDir();
|
|
651
|
+
if (!fs5.existsSync(dataDir)) {
|
|
652
|
+
fs5.mkdirSync(dataDir, { recursive: true });
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function loadApiKeyData() {
|
|
656
|
+
const logger6 = getLogger();
|
|
657
|
+
const apiKeyPath = getApiKeyFilePath();
|
|
658
|
+
try {
|
|
659
|
+
if (!fs5.existsSync(apiKeyPath)) {
|
|
660
|
+
logger6.debug("No API key file found", { path: apiKeyPath });
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
const content = fs5.readFileSync(apiKeyPath, "utf-8");
|
|
664
|
+
const data = JSON.parse(content);
|
|
665
|
+
return data;
|
|
666
|
+
} catch (error) {
|
|
667
|
+
logger6.warn("Failed to load API key data", {
|
|
668
|
+
error: error instanceof Error ? error.message : String(error)
|
|
669
|
+
});
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
function saveApiKeyData(data) {
|
|
674
|
+
const logger6 = getLogger();
|
|
675
|
+
const apiKeyPath = getApiKeyFilePath();
|
|
676
|
+
try {
|
|
677
|
+
ensureDataDir();
|
|
678
|
+
const content = JSON.stringify(data, null, 2);
|
|
679
|
+
fs5.writeFileSync(apiKeyPath, content, { mode: 384 });
|
|
680
|
+
logger6.info("API key saved", { path: apiKeyPath });
|
|
681
|
+
} catch (error) {
|
|
682
|
+
logger6.error("Failed to save API key", {
|
|
683
|
+
error: error instanceof Error ? error.message : String(error)
|
|
684
|
+
});
|
|
685
|
+
throw error;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function deleteApiKeyData() {
|
|
689
|
+
const logger6 = getLogger();
|
|
690
|
+
const apiKeyPath = getApiKeyFilePath();
|
|
691
|
+
try {
|
|
692
|
+
if (fs5.existsSync(apiKeyPath)) {
|
|
693
|
+
fs5.unlinkSync(apiKeyPath);
|
|
694
|
+
logger6.info("API key deleted", { path: apiKeyPath });
|
|
695
|
+
}
|
|
696
|
+
} catch (error) {
|
|
697
|
+
logger6.warn("Failed to delete API key", {
|
|
698
|
+
error: error instanceof Error ? error.message : String(error)
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function getValidApiKeyData() {
|
|
703
|
+
const data = loadApiKeyData();
|
|
704
|
+
if (!data) {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
if (data.apiKey) {
|
|
708
|
+
return data;
|
|
709
|
+
}
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
function hasApiKey() {
|
|
713
|
+
const data = loadApiKeyData();
|
|
714
|
+
return !!data?.apiKey;
|
|
715
|
+
}
|
|
716
|
+
function getApiKey() {
|
|
717
|
+
const data = loadApiKeyData();
|
|
718
|
+
return data?.apiKey ?? null;
|
|
719
|
+
}
|
|
720
|
+
function saveApiKey(params) {
|
|
721
|
+
const logger6 = getLogger();
|
|
722
|
+
const apiKeyPath = getApiKeyFilePath();
|
|
723
|
+
try {
|
|
724
|
+
ensureDataDir();
|
|
725
|
+
const data = {
|
|
726
|
+
accessToken: "",
|
|
727
|
+
expiresAt: "",
|
|
728
|
+
apiKey: params.apiKey,
|
|
729
|
+
apiKeyId: params.apiKeyId
|
|
730
|
+
};
|
|
731
|
+
const content = JSON.stringify(data, null, 2);
|
|
732
|
+
fs5.writeFileSync(apiKeyPath, content, { mode: 384 });
|
|
733
|
+
logger6.info("API key saved", { path: apiKeyPath });
|
|
734
|
+
} catch (error) {
|
|
735
|
+
logger6.error("Failed to save API key", {
|
|
736
|
+
error: error instanceof Error ? error.message : String(error)
|
|
737
|
+
});
|
|
738
|
+
throw error;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
var loadCredentials = loadApiKeyData;
|
|
742
|
+
var saveCredentials = saveApiKeyData;
|
|
743
|
+
var deleteCredentials = deleteApiKeyData;
|
|
744
|
+
var getValidCredentials = getValidApiKeyData;
|
|
745
|
+
var getCredentialsFilePath = getApiKeyFilePath;
|
|
746
|
+
|
|
747
|
+
// packages/mcps/src/mcp/local/types/enums.ts
|
|
748
|
+
var DeviceCodePollStatus = /* @__PURE__ */ ((DeviceCodePollStatus2) => {
|
|
749
|
+
DeviceCodePollStatus2["Pending"] = "pending";
|
|
750
|
+
DeviceCodePollStatus2["Complete"] = "complete";
|
|
751
|
+
DeviceCodePollStatus2["Expired"] = "expired";
|
|
752
|
+
DeviceCodePollStatus2["Error"] = "error";
|
|
753
|
+
return DeviceCodePollStatus2;
|
|
754
|
+
})(DeviceCodePollStatus || {});
|
|
755
|
+
var SessionStatus = /* @__PURE__ */ ((SessionStatus2) => {
|
|
756
|
+
SessionStatus2["Running"] = "running";
|
|
757
|
+
SessionStatus2["Completed"] = "completed";
|
|
758
|
+
SessionStatus2["Failed"] = "failed";
|
|
759
|
+
return SessionStatus2;
|
|
760
|
+
})(SessionStatus || {});
|
|
761
|
+
var LocalRunStatus = /* @__PURE__ */ ((LocalRunStatus2) => {
|
|
762
|
+
LocalRunStatus2["PENDING"] = "pending";
|
|
763
|
+
LocalRunStatus2["RUNNING"] = "running";
|
|
764
|
+
LocalRunStatus2["PASSED"] = "passed";
|
|
765
|
+
LocalRunStatus2["FAILED"] = "failed";
|
|
766
|
+
LocalRunStatus2["CANCELLED"] = "cancelled";
|
|
624
767
|
return LocalRunStatus2;
|
|
625
768
|
})(LocalRunStatus || {});
|
|
626
769
|
var LocalRunType = /* @__PURE__ */ ((LocalRunType2) => {
|
|
@@ -699,16 +842,16 @@ var AuthService = class {
|
|
|
699
842
|
* Get current authentication status.
|
|
700
843
|
*/
|
|
701
844
|
getAuthStatus() {
|
|
702
|
-
const
|
|
845
|
+
const logger6 = getLogger();
|
|
703
846
|
const storedAuth = this.loadStoredAuth();
|
|
704
847
|
if (!storedAuth) {
|
|
705
|
-
|
|
848
|
+
logger6.debug("No stored auth found");
|
|
706
849
|
return { authenticated: false };
|
|
707
850
|
}
|
|
708
851
|
const now = /* @__PURE__ */ new Date();
|
|
709
852
|
const expiresAt = new Date(storedAuth.expiresAt);
|
|
710
853
|
const isExpired = now >= expiresAt;
|
|
711
|
-
|
|
854
|
+
logger6.debug("Auth status checked", {
|
|
712
855
|
email: storedAuth.email,
|
|
713
856
|
isExpired,
|
|
714
857
|
expiresAt: storedAuth.expiresAt
|
|
@@ -726,10 +869,10 @@ var AuthService = class {
|
|
|
726
869
|
* @param options.forceNewSession - Clear existing Auth0 browser session before login to allow account switching.
|
|
727
870
|
*/
|
|
728
871
|
async startDeviceCodeFlow(options) {
|
|
729
|
-
const
|
|
872
|
+
const logger6 = getLogger();
|
|
730
873
|
const config = getConfig();
|
|
731
874
|
const { domain, clientId, audience, scopes } = config.localQa.auth0;
|
|
732
|
-
|
|
875
|
+
logger6.info("Starting device code flow");
|
|
733
876
|
const url = `https://${domain}/oauth/device/code`;
|
|
734
877
|
const body = new URLSearchParams({
|
|
735
878
|
client_id: clientId,
|
|
@@ -745,14 +888,14 @@ var AuthService = class {
|
|
|
745
888
|
});
|
|
746
889
|
if (!response.ok) {
|
|
747
890
|
const errorText = await response.text();
|
|
748
|
-
|
|
891
|
+
logger6.error("Device code request failed", {
|
|
749
892
|
status: response.status,
|
|
750
893
|
error: errorText
|
|
751
894
|
});
|
|
752
895
|
throw new Error(`Failed to start device code flow: ${response.status} ${errorText}`);
|
|
753
896
|
}
|
|
754
897
|
const data = await response.json();
|
|
755
|
-
|
|
898
|
+
logger6.info("Device code flow started", {
|
|
756
899
|
userCode: data.user_code,
|
|
757
900
|
verificationUri: data.verification_uri,
|
|
758
901
|
expiresIn: data.expires_in
|
|
@@ -768,7 +911,7 @@ var AuthService = class {
|
|
|
768
911
|
logoutUrl.searchParams.set("client_id", clientId);
|
|
769
912
|
logoutUrl.searchParams.set("returnTo", data.verification_uri_complete);
|
|
770
913
|
browserUrl = logoutUrl.toString();
|
|
771
|
-
|
|
914
|
+
logger6.info("Force new session: opening logout-redirect URL", {
|
|
772
915
|
logoutUrl: browserUrl
|
|
773
916
|
});
|
|
774
917
|
}
|
|
@@ -776,9 +919,9 @@ var AuthService = class {
|
|
|
776
919
|
url: browserUrl
|
|
777
920
|
});
|
|
778
921
|
if (browserOpenResult.opened) {
|
|
779
|
-
|
|
922
|
+
logger6.info("Browser opened for device code login");
|
|
780
923
|
} else {
|
|
781
|
-
|
|
924
|
+
logger6.warn("Failed to open browser for device code login", {
|
|
782
925
|
error: browserOpenResult.error,
|
|
783
926
|
url: browserUrl
|
|
784
927
|
});
|
|
@@ -798,39 +941,39 @@ var AuthService = class {
|
|
|
798
941
|
* Store a pending device code for later retrieval.
|
|
799
942
|
*/
|
|
800
943
|
storePendingDeviceCode(params) {
|
|
801
|
-
const
|
|
944
|
+
const logger6 = getLogger();
|
|
802
945
|
const dir = path2.dirname(this.pendingDeviceCodePath);
|
|
803
|
-
if (!
|
|
804
|
-
|
|
946
|
+
if (!fs5.existsSync(dir)) {
|
|
947
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
805
948
|
}
|
|
806
|
-
|
|
949
|
+
fs5.writeFileSync(this.pendingDeviceCodePath, JSON.stringify(params, null, 2), {
|
|
807
950
|
encoding: "utf-8",
|
|
808
951
|
mode: 384
|
|
809
952
|
});
|
|
810
|
-
|
|
953
|
+
logger6.debug("Pending device code stored", { userCode: params.userCode });
|
|
811
954
|
}
|
|
812
955
|
/**
|
|
813
956
|
* Get the pending device code if one exists and is not expired.
|
|
814
957
|
*/
|
|
815
958
|
getPendingDeviceCode() {
|
|
816
|
-
const
|
|
817
|
-
if (!
|
|
818
|
-
|
|
959
|
+
const logger6 = getLogger();
|
|
960
|
+
if (!fs5.existsSync(this.pendingDeviceCodePath)) {
|
|
961
|
+
logger6.debug("No pending device code found");
|
|
819
962
|
return null;
|
|
820
963
|
}
|
|
821
964
|
try {
|
|
822
|
-
const content =
|
|
965
|
+
const content = fs5.readFileSync(this.pendingDeviceCodePath, "utf-8");
|
|
823
966
|
const data = JSON.parse(content);
|
|
824
967
|
const now = /* @__PURE__ */ new Date();
|
|
825
968
|
const expiresAt = new Date(data.expiresAt);
|
|
826
969
|
if (now >= expiresAt) {
|
|
827
|
-
|
|
970
|
+
logger6.debug("Pending device code expired");
|
|
828
971
|
this.clearPendingDeviceCode();
|
|
829
972
|
return null;
|
|
830
973
|
}
|
|
831
974
|
return data.deviceCode;
|
|
832
975
|
} catch (error) {
|
|
833
|
-
|
|
976
|
+
logger6.warn("Failed to read pending device code", {
|
|
834
977
|
error: error instanceof Error ? error.message : String(error)
|
|
835
978
|
});
|
|
836
979
|
return null;
|
|
@@ -840,13 +983,13 @@ var AuthService = class {
|
|
|
840
983
|
* Clear the pending device code file.
|
|
841
984
|
*/
|
|
842
985
|
clearPendingDeviceCode() {
|
|
843
|
-
const
|
|
844
|
-
if (
|
|
986
|
+
const logger6 = getLogger();
|
|
987
|
+
if (fs5.existsSync(this.pendingDeviceCodePath)) {
|
|
845
988
|
try {
|
|
846
|
-
|
|
847
|
-
|
|
989
|
+
fs5.unlinkSync(this.pendingDeviceCodePath);
|
|
990
|
+
logger6.debug("Pending device code cleared");
|
|
848
991
|
} catch (error) {
|
|
849
|
-
|
|
992
|
+
logger6.warn("Failed to clear pending device code", {
|
|
850
993
|
error: error instanceof Error ? error.message : String(error)
|
|
851
994
|
});
|
|
852
995
|
}
|
|
@@ -856,10 +999,10 @@ var AuthService = class {
|
|
|
856
999
|
* Poll for device code authorization completion.
|
|
857
1000
|
*/
|
|
858
1001
|
async pollDeviceCode(deviceCode) {
|
|
859
|
-
const
|
|
1002
|
+
const logger6 = getLogger();
|
|
860
1003
|
const config = getConfig();
|
|
861
1004
|
const { domain, clientId } = config.localQa.auth0;
|
|
862
|
-
|
|
1005
|
+
logger6.debug("Polling for device code authorization");
|
|
863
1006
|
const url = `https://${domain}/oauth/token`;
|
|
864
1007
|
const body = new URLSearchParams({
|
|
865
1008
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
@@ -889,7 +1032,7 @@ var AuthService = class {
|
|
|
889
1032
|
userId: userInfo.sub
|
|
890
1033
|
});
|
|
891
1034
|
this.clearPendingDeviceCode();
|
|
892
|
-
|
|
1035
|
+
logger6.info("Device code authorization complete", { email: userInfo.email });
|
|
893
1036
|
return {
|
|
894
1037
|
status: "complete" /* Complete */,
|
|
895
1038
|
message: "Authentication successful!",
|
|
@@ -898,35 +1041,35 @@ var AuthService = class {
|
|
|
898
1041
|
}
|
|
899
1042
|
const errorData = await response.json();
|
|
900
1043
|
if (errorData.error === "authorization_pending") {
|
|
901
|
-
|
|
1044
|
+
logger6.debug("Authorization pending");
|
|
902
1045
|
return {
|
|
903
1046
|
status: "pending" /* Pending */,
|
|
904
1047
|
message: "Waiting for user to complete authorization..."
|
|
905
1048
|
};
|
|
906
1049
|
}
|
|
907
1050
|
if (errorData.error === "slow_down") {
|
|
908
|
-
|
|
1051
|
+
logger6.debug("Polling too fast");
|
|
909
1052
|
return {
|
|
910
1053
|
status: "pending" /* Pending */,
|
|
911
1054
|
message: "Polling too fast, slowing down..."
|
|
912
1055
|
};
|
|
913
1056
|
}
|
|
914
1057
|
if (errorData.error === "expired_token") {
|
|
915
|
-
|
|
1058
|
+
logger6.warn("Device code expired");
|
|
916
1059
|
return {
|
|
917
1060
|
status: "expired" /* Expired */,
|
|
918
1061
|
message: "The authorization code has expired. Please start again."
|
|
919
1062
|
};
|
|
920
1063
|
}
|
|
921
1064
|
if (errorData.error === "access_denied") {
|
|
922
|
-
|
|
1065
|
+
logger6.warn("Access denied");
|
|
923
1066
|
return {
|
|
924
1067
|
status: "error" /* Error */,
|
|
925
1068
|
message: "Access was denied by the user.",
|
|
926
1069
|
error: errorData.error_description ?? errorData.error
|
|
927
1070
|
};
|
|
928
1071
|
}
|
|
929
|
-
|
|
1072
|
+
logger6.error("Unexpected error during polling", { error: errorData });
|
|
930
1073
|
return {
|
|
931
1074
|
status: "error" /* Error */,
|
|
932
1075
|
message: errorData.error_description ?? errorData.error,
|
|
@@ -934,7 +1077,7 @@ var AuthService = class {
|
|
|
934
1077
|
};
|
|
935
1078
|
} catch (error) {
|
|
936
1079
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
937
|
-
|
|
1080
|
+
logger6.error("Poll request failed", { error: errorMessage });
|
|
938
1081
|
return {
|
|
939
1082
|
status: "error" /* Error */,
|
|
940
1083
|
message: `Poll request failed: ${errorMessage}`,
|
|
@@ -946,11 +1089,11 @@ var AuthService = class {
|
|
|
946
1089
|
* Poll for device code authorization until completion or timeout.
|
|
947
1090
|
*/
|
|
948
1091
|
async waitForDeviceCodeAuthorization(params) {
|
|
949
|
-
const
|
|
1092
|
+
const logger6 = getLogger();
|
|
950
1093
|
const timeoutMs = params.timeoutMs ?? DEFAULT_LOGIN_WAIT_TIMEOUT_MS;
|
|
951
1094
|
const pollIntervalMs = Math.max(params.intervalSeconds, 1) * 1e3;
|
|
952
1095
|
const startedAt = Date.now();
|
|
953
|
-
|
|
1096
|
+
logger6.info("Waiting for device code authorization", {
|
|
954
1097
|
timeoutMs,
|
|
955
1098
|
pollIntervalMs
|
|
956
1099
|
});
|
|
@@ -974,7 +1117,7 @@ var AuthService = class {
|
|
|
974
1117
|
* Get user info from Auth0.
|
|
975
1118
|
*/
|
|
976
1119
|
async getUserInfo(accessToken) {
|
|
977
|
-
const
|
|
1120
|
+
const logger6 = getLogger();
|
|
978
1121
|
const config = getConfig();
|
|
979
1122
|
const { domain } = config.localQa.auth0;
|
|
980
1123
|
const url = `https://${domain}/userinfo`;
|
|
@@ -986,13 +1129,13 @@ var AuthService = class {
|
|
|
986
1129
|
}
|
|
987
1130
|
});
|
|
988
1131
|
if (!response.ok) {
|
|
989
|
-
|
|
1132
|
+
logger6.warn("Failed to get user info", { status: response.status });
|
|
990
1133
|
return {};
|
|
991
1134
|
}
|
|
992
1135
|
const data = await response.json();
|
|
993
1136
|
return data;
|
|
994
1137
|
} catch (error) {
|
|
995
|
-
|
|
1138
|
+
logger6.warn("User info request failed", {
|
|
996
1139
|
error: error instanceof Error ? error.message : String(error)
|
|
997
1140
|
});
|
|
998
1141
|
return {};
|
|
@@ -1003,7 +1146,7 @@ var AuthService = class {
|
|
|
1003
1146
|
*/
|
|
1004
1147
|
async storeAuth(params) {
|
|
1005
1148
|
const { tokenResponse, email, userId } = params;
|
|
1006
|
-
const
|
|
1149
|
+
const logger6 = getLogger();
|
|
1007
1150
|
const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1e3).toISOString();
|
|
1008
1151
|
const storedAuth = {
|
|
1009
1152
|
accessToken: tokenResponse.accessToken,
|
|
@@ -1013,28 +1156,28 @@ var AuthService = class {
|
|
|
1013
1156
|
userId
|
|
1014
1157
|
};
|
|
1015
1158
|
const dir = path2.dirname(this.oauthSessionFilePath);
|
|
1016
|
-
if (!
|
|
1017
|
-
|
|
1159
|
+
if (!fs5.existsSync(dir)) {
|
|
1160
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1018
1161
|
}
|
|
1019
|
-
|
|
1162
|
+
fs5.writeFileSync(this.oauthSessionFilePath, JSON.stringify(storedAuth, null, 2), {
|
|
1020
1163
|
encoding: "utf-8",
|
|
1021
1164
|
mode: 384
|
|
1022
1165
|
});
|
|
1023
|
-
|
|
1166
|
+
logger6.info("Auth stored successfully", { email, expiresAt });
|
|
1024
1167
|
}
|
|
1025
1168
|
/**
|
|
1026
1169
|
* Load stored authentication.
|
|
1027
1170
|
*/
|
|
1028
1171
|
loadStoredAuth() {
|
|
1029
|
-
const
|
|
1030
|
-
if (!
|
|
1172
|
+
const logger6 = getLogger();
|
|
1173
|
+
if (!fs5.existsSync(this.oauthSessionFilePath)) {
|
|
1031
1174
|
return null;
|
|
1032
1175
|
}
|
|
1033
1176
|
try {
|
|
1034
|
-
const content =
|
|
1177
|
+
const content = fs5.readFileSync(this.oauthSessionFilePath, "utf-8");
|
|
1035
1178
|
return JSON.parse(content);
|
|
1036
1179
|
} catch (error) {
|
|
1037
|
-
|
|
1180
|
+
logger6.error("Failed to load stored auth", {
|
|
1038
1181
|
error: error instanceof Error ? error.message : String(error)
|
|
1039
1182
|
});
|
|
1040
1183
|
return null;
|
|
@@ -1075,15 +1218,15 @@ var AuthService = class {
|
|
|
1075
1218
|
* @returns New access token or null if refresh failed.
|
|
1076
1219
|
*/
|
|
1077
1220
|
async refreshAccessToken() {
|
|
1078
|
-
const
|
|
1221
|
+
const logger6 = getLogger();
|
|
1079
1222
|
const storedAuth = this.loadStoredAuth();
|
|
1080
1223
|
if (!storedAuth?.refreshToken) {
|
|
1081
|
-
|
|
1224
|
+
logger6.debug("No refresh token available");
|
|
1082
1225
|
return null;
|
|
1083
1226
|
}
|
|
1084
1227
|
const config = getConfig();
|
|
1085
1228
|
const { domain, clientId } = config.localQa.auth0;
|
|
1086
|
-
|
|
1229
|
+
logger6.info("Refreshing access token");
|
|
1087
1230
|
const url = `https://${domain}/oauth/token`;
|
|
1088
1231
|
const body = new URLSearchParams({
|
|
1089
1232
|
grant_type: "refresh_token",
|
|
@@ -1100,7 +1243,7 @@ var AuthService = class {
|
|
|
1100
1243
|
});
|
|
1101
1244
|
if (!response.ok) {
|
|
1102
1245
|
const errorText = await response.text();
|
|
1103
|
-
|
|
1246
|
+
logger6.error("Token refresh failed", {
|
|
1104
1247
|
status: response.status,
|
|
1105
1248
|
error: errorText
|
|
1106
1249
|
});
|
|
@@ -1116,17 +1259,17 @@ var AuthService = class {
|
|
|
1116
1259
|
userId: storedAuth.userId
|
|
1117
1260
|
};
|
|
1118
1261
|
const dir = path2.dirname(this.oauthSessionFilePath);
|
|
1119
|
-
if (!
|
|
1120
|
-
|
|
1262
|
+
if (!fs5.existsSync(dir)) {
|
|
1263
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1121
1264
|
}
|
|
1122
|
-
|
|
1265
|
+
fs5.writeFileSync(this.oauthSessionFilePath, JSON.stringify(updatedAuth, null, 2), {
|
|
1123
1266
|
encoding: "utf-8",
|
|
1124
1267
|
mode: 384
|
|
1125
1268
|
});
|
|
1126
|
-
|
|
1269
|
+
logger6.info("Access token refreshed", { expiresAt: newExpiresAt });
|
|
1127
1270
|
return tokenData.access_token;
|
|
1128
1271
|
} catch (error) {
|
|
1129
|
-
|
|
1272
|
+
logger6.error("Token refresh request failed", {
|
|
1130
1273
|
error: error instanceof Error ? error.message : String(error)
|
|
1131
1274
|
});
|
|
1132
1275
|
return null;
|
|
@@ -1138,10 +1281,10 @@ var AuthService = class {
|
|
|
1138
1281
|
* @returns Valid access token or null if not authenticated or refresh failed.
|
|
1139
1282
|
*/
|
|
1140
1283
|
async getValidAccessToken() {
|
|
1141
|
-
const
|
|
1284
|
+
const logger6 = getLogger();
|
|
1142
1285
|
const storedAuth = this.loadStoredAuth();
|
|
1143
1286
|
if (!storedAuth) {
|
|
1144
|
-
|
|
1287
|
+
logger6.debug("No stored auth, cannot get valid token");
|
|
1145
1288
|
return null;
|
|
1146
1289
|
}
|
|
1147
1290
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1151,36 +1294,36 @@ var AuthService = class {
|
|
|
1151
1294
|
return storedAuth.accessToken;
|
|
1152
1295
|
}
|
|
1153
1296
|
if (!isStrictlyExpired) {
|
|
1154
|
-
|
|
1297
|
+
logger6.debug("Access token in buffer zone, attempting proactive refresh");
|
|
1155
1298
|
} else {
|
|
1156
|
-
|
|
1299
|
+
logger6.info("Access token expired, attempting refresh");
|
|
1157
1300
|
}
|
|
1158
1301
|
const refreshedToken = await this.refreshAccessToken();
|
|
1159
1302
|
if (refreshedToken) {
|
|
1160
1303
|
return refreshedToken;
|
|
1161
1304
|
}
|
|
1162
1305
|
if (!isStrictlyExpired) {
|
|
1163
|
-
|
|
1306
|
+
logger6.warn("Token refresh failed, but token not yet expired - using existing token");
|
|
1164
1307
|
return storedAuth.accessToken;
|
|
1165
1308
|
}
|
|
1166
|
-
|
|
1309
|
+
logger6.warn("Token refresh failed and token is expired, user needs to re-authenticate");
|
|
1167
1310
|
return null;
|
|
1168
1311
|
}
|
|
1169
1312
|
/**
|
|
1170
1313
|
* Clear stored authentication (logout).
|
|
1171
1314
|
*/
|
|
1172
1315
|
logout() {
|
|
1173
|
-
const
|
|
1174
|
-
if (!
|
|
1175
|
-
|
|
1316
|
+
const logger6 = getLogger();
|
|
1317
|
+
if (!fs5.existsSync(this.oauthSessionFilePath)) {
|
|
1318
|
+
logger6.debug("No auth to clear");
|
|
1176
1319
|
return false;
|
|
1177
1320
|
}
|
|
1178
1321
|
try {
|
|
1179
|
-
|
|
1180
|
-
|
|
1322
|
+
fs5.unlinkSync(this.oauthSessionFilePath);
|
|
1323
|
+
logger6.info("Auth cleared successfully");
|
|
1181
1324
|
return true;
|
|
1182
1325
|
} catch (error) {
|
|
1183
|
-
|
|
1326
|
+
logger6.error("Failed to clear auth", {
|
|
1184
1327
|
error: error instanceof Error ? error.message : String(error)
|
|
1185
1328
|
});
|
|
1186
1329
|
return false;
|
|
@@ -1196,9 +1339,9 @@ function resetAuthService() {
|
|
|
1196
1339
|
serviceInstance = null;
|
|
1197
1340
|
}
|
|
1198
1341
|
function sleep(params) {
|
|
1199
|
-
return new Promise((
|
|
1342
|
+
return new Promise((resolve3) => {
|
|
1200
1343
|
setTimeout(() => {
|
|
1201
|
-
|
|
1344
|
+
resolve3();
|
|
1202
1345
|
}, params.durationMs);
|
|
1203
1346
|
});
|
|
1204
1347
|
}
|
|
@@ -1220,14 +1363,14 @@ var StorageService = class {
|
|
|
1220
1363
|
* Ensure the base directories exist.
|
|
1221
1364
|
*/
|
|
1222
1365
|
ensureDirectories() {
|
|
1223
|
-
const
|
|
1224
|
-
if (!
|
|
1225
|
-
|
|
1226
|
-
|
|
1366
|
+
const logger6 = getLogger();
|
|
1367
|
+
if (!fs5.existsSync(this.dataDir)) {
|
|
1368
|
+
fs5.mkdirSync(this.dataDir, { recursive: true });
|
|
1369
|
+
logger6.info("Created data directory", { path: this.dataDir });
|
|
1227
1370
|
}
|
|
1228
|
-
if (!
|
|
1229
|
-
|
|
1230
|
-
|
|
1371
|
+
if (!fs5.existsSync(this.sessionsDir)) {
|
|
1372
|
+
fs5.mkdirSync(this.sessionsDir, { recursive: true });
|
|
1373
|
+
logger6.info("Created sessions directory", { path: this.sessionsDir });
|
|
1231
1374
|
}
|
|
1232
1375
|
}
|
|
1233
1376
|
/**
|
|
@@ -1236,14 +1379,14 @@ var StorageService = class {
|
|
|
1236
1379
|
* @returns Path to the session directory.
|
|
1237
1380
|
*/
|
|
1238
1381
|
createSessionDirectory(sessionId) {
|
|
1239
|
-
const
|
|
1382
|
+
const logger6 = getLogger();
|
|
1240
1383
|
this.ensureDirectories();
|
|
1241
1384
|
const sessionDir = path2.join(this.sessionsDir, sessionId);
|
|
1242
|
-
if (!
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1385
|
+
if (!fs5.existsSync(sessionDir)) {
|
|
1386
|
+
fs5.mkdirSync(sessionDir, { recursive: true });
|
|
1387
|
+
fs5.mkdirSync(path2.join(sessionDir, "screenshots"), { recursive: true });
|
|
1388
|
+
fs5.mkdirSync(path2.join(sessionDir, "logs"), { recursive: true });
|
|
1389
|
+
logger6.info("Created session directory", { sessionId, path: sessionDir });
|
|
1247
1390
|
}
|
|
1248
1391
|
return sessionDir;
|
|
1249
1392
|
}
|
|
@@ -1252,11 +1395,11 @@ var StorageService = class {
|
|
|
1252
1395
|
* @param metadata - Session metadata to save.
|
|
1253
1396
|
*/
|
|
1254
1397
|
saveSessionMetadata(metadata) {
|
|
1255
|
-
const
|
|
1398
|
+
const logger6 = getLogger();
|
|
1256
1399
|
const sessionDir = this.createSessionDirectory(metadata.sessionId);
|
|
1257
1400
|
const metadataPath = path2.join(sessionDir, "metadata.json");
|
|
1258
|
-
|
|
1259
|
-
|
|
1401
|
+
fs5.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
1402
|
+
logger6.debug("Saved session metadata", { sessionId: metadata.sessionId });
|
|
1260
1403
|
}
|
|
1261
1404
|
/**
|
|
1262
1405
|
* Load session metadata.
|
|
@@ -1264,16 +1407,16 @@ var StorageService = class {
|
|
|
1264
1407
|
* @returns Session metadata, or null if not found.
|
|
1265
1408
|
*/
|
|
1266
1409
|
loadSessionMetadata(sessionId) {
|
|
1267
|
-
const
|
|
1410
|
+
const logger6 = getLogger();
|
|
1268
1411
|
const metadataPath = path2.join(this.sessionsDir, sessionId, "metadata.json");
|
|
1269
|
-
if (!
|
|
1412
|
+
if (!fs5.existsSync(metadataPath)) {
|
|
1270
1413
|
return null;
|
|
1271
1414
|
}
|
|
1272
1415
|
try {
|
|
1273
|
-
const content =
|
|
1416
|
+
const content = fs5.readFileSync(metadataPath, "utf-8");
|
|
1274
1417
|
return JSON.parse(content);
|
|
1275
1418
|
} catch (error) {
|
|
1276
|
-
|
|
1419
|
+
logger6.error("Failed to load session metadata", {
|
|
1277
1420
|
sessionId,
|
|
1278
1421
|
error: error instanceof Error ? error.message : String(error)
|
|
1279
1422
|
});
|
|
@@ -1285,12 +1428,12 @@ var StorageService = class {
|
|
|
1285
1428
|
* @returns Array of session IDs.
|
|
1286
1429
|
*/
|
|
1287
1430
|
listSessions() {
|
|
1288
|
-
if (!
|
|
1431
|
+
if (!fs5.existsSync(this.sessionsDir)) {
|
|
1289
1432
|
return [];
|
|
1290
1433
|
}
|
|
1291
|
-
return
|
|
1434
|
+
return fs5.readdirSync(this.sessionsDir).filter((entry) => {
|
|
1292
1435
|
const entryPath = path2.join(this.sessionsDir, entry);
|
|
1293
|
-
return
|
|
1436
|
+
return fs5.statSync(entryPath).isDirectory();
|
|
1294
1437
|
});
|
|
1295
1438
|
}
|
|
1296
1439
|
/**
|
|
@@ -1299,11 +1442,11 @@ var StorageService = class {
|
|
|
1299
1442
|
*/
|
|
1300
1443
|
getCurrentSessionId() {
|
|
1301
1444
|
const currentPath = path2.join(this.sessionsDir, "current");
|
|
1302
|
-
if (!
|
|
1445
|
+
if (!fs5.existsSync(currentPath)) {
|
|
1303
1446
|
return null;
|
|
1304
1447
|
}
|
|
1305
1448
|
try {
|
|
1306
|
-
const target =
|
|
1449
|
+
const target = fs5.readlinkSync(currentPath);
|
|
1307
1450
|
return path2.basename(target);
|
|
1308
1451
|
} catch {
|
|
1309
1452
|
return null;
|
|
@@ -1314,14 +1457,14 @@ var StorageService = class {
|
|
|
1314
1457
|
* @param sessionId - Session ID to set as current.
|
|
1315
1458
|
*/
|
|
1316
1459
|
setCurrentSession(sessionId) {
|
|
1317
|
-
const
|
|
1460
|
+
const logger6 = getLogger();
|
|
1318
1461
|
const currentPath = path2.join(this.sessionsDir, "current");
|
|
1319
1462
|
const targetPath = path2.join(this.sessionsDir, sessionId);
|
|
1320
|
-
if (
|
|
1321
|
-
|
|
1463
|
+
if (fs5.existsSync(currentPath)) {
|
|
1464
|
+
fs5.unlinkSync(currentPath);
|
|
1322
1465
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1466
|
+
fs5.symlinkSync(targetPath, currentPath);
|
|
1467
|
+
logger6.info("Set current session", { sessionId });
|
|
1325
1468
|
}
|
|
1326
1469
|
/**
|
|
1327
1470
|
* Save a screenshot to the session directory.
|
|
@@ -1329,11 +1472,11 @@ var StorageService = class {
|
|
|
1329
1472
|
*/
|
|
1330
1473
|
saveScreenshot(params) {
|
|
1331
1474
|
const { sessionId, filename, data } = params;
|
|
1332
|
-
const
|
|
1475
|
+
const logger6 = getLogger();
|
|
1333
1476
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1334
1477
|
const screenshotPath = path2.join(sessionDir, "screenshots", filename);
|
|
1335
|
-
|
|
1336
|
-
|
|
1478
|
+
fs5.writeFileSync(screenshotPath, data);
|
|
1479
|
+
logger6.debug("Saved screenshot", { sessionId, filename });
|
|
1337
1480
|
return screenshotPath;
|
|
1338
1481
|
}
|
|
1339
1482
|
/**
|
|
@@ -1342,11 +1485,11 @@ var StorageService = class {
|
|
|
1342
1485
|
*/
|
|
1343
1486
|
appendToResults(params) {
|
|
1344
1487
|
const { sessionId, content } = params;
|
|
1345
|
-
const
|
|
1488
|
+
const logger6 = getLogger();
|
|
1346
1489
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1347
1490
|
const resultsPath = path2.join(sessionDir, "results.md");
|
|
1348
|
-
|
|
1349
|
-
|
|
1491
|
+
fs5.appendFileSync(resultsPath, content + "\n", "utf-8");
|
|
1492
|
+
logger6.debug("Appended to results", { sessionId });
|
|
1350
1493
|
}
|
|
1351
1494
|
/**
|
|
1352
1495
|
* Get the results markdown content.
|
|
@@ -1355,10 +1498,10 @@ var StorageService = class {
|
|
|
1355
1498
|
*/
|
|
1356
1499
|
getResults(sessionId) {
|
|
1357
1500
|
const resultsPath = path2.join(this.sessionsDir, sessionId, "results.md");
|
|
1358
|
-
if (!
|
|
1501
|
+
if (!fs5.existsSync(resultsPath)) {
|
|
1359
1502
|
return null;
|
|
1360
1503
|
}
|
|
1361
|
-
return
|
|
1504
|
+
return fs5.readFileSync(resultsPath, "utf-8");
|
|
1362
1505
|
}
|
|
1363
1506
|
/**
|
|
1364
1507
|
* Get the data directory path.
|
|
@@ -1381,7 +1524,7 @@ var StorageService = class {
|
|
|
1381
1524
|
*/
|
|
1382
1525
|
createSession(params) {
|
|
1383
1526
|
const { sessionId, targetUrl, testInstructions } = params;
|
|
1384
|
-
const
|
|
1527
|
+
const logger6 = getLogger();
|
|
1385
1528
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1386
1529
|
const metadata = {
|
|
1387
1530
|
sessionId,
|
|
@@ -1393,7 +1536,7 @@ var StorageService = class {
|
|
|
1393
1536
|
};
|
|
1394
1537
|
this.saveSessionMetadata(metadata);
|
|
1395
1538
|
this.setCurrentSession(sessionId);
|
|
1396
|
-
|
|
1539
|
+
logger6.info("Created session", { sessionId, targetUrl });
|
|
1397
1540
|
return sessionDir;
|
|
1398
1541
|
}
|
|
1399
1542
|
/**
|
|
@@ -1402,10 +1545,10 @@ var StorageService = class {
|
|
|
1402
1545
|
*/
|
|
1403
1546
|
updateSessionStatus(params) {
|
|
1404
1547
|
const { sessionId, status } = params;
|
|
1405
|
-
const
|
|
1548
|
+
const logger6 = getLogger();
|
|
1406
1549
|
const metadata = this.loadSessionMetadata(sessionId);
|
|
1407
1550
|
if (!metadata) {
|
|
1408
|
-
|
|
1551
|
+
logger6.warn("Session not found for status update", { sessionId });
|
|
1409
1552
|
return;
|
|
1410
1553
|
}
|
|
1411
1554
|
metadata.status = status;
|
|
@@ -1413,7 +1556,7 @@ var StorageService = class {
|
|
|
1413
1556
|
metadata.endTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1414
1557
|
}
|
|
1415
1558
|
this.saveSessionMetadata(metadata);
|
|
1416
|
-
|
|
1559
|
+
logger6.debug("Updated session status", { sessionId, status });
|
|
1417
1560
|
}
|
|
1418
1561
|
/**
|
|
1419
1562
|
* Initialize the results.md file with a header.
|
|
@@ -1421,10 +1564,10 @@ var StorageService = class {
|
|
|
1421
1564
|
*/
|
|
1422
1565
|
initializeResults(params) {
|
|
1423
1566
|
const { sessionId, targetUrl, testInstructions } = params;
|
|
1424
|
-
const
|
|
1567
|
+
const logger6 = getLogger();
|
|
1425
1568
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1426
1569
|
const resultsPath = path2.join(sessionDir, "results.md");
|
|
1427
|
-
const
|
|
1570
|
+
const header = [
|
|
1428
1571
|
`# Test Results: ${sessionId}`,
|
|
1429
1572
|
"",
|
|
1430
1573
|
`**Target URL:** ${targetUrl}`,
|
|
@@ -1436,8 +1579,8 @@ var StorageService = class {
|
|
|
1436
1579
|
"## Test Steps",
|
|
1437
1580
|
""
|
|
1438
1581
|
].join("\n");
|
|
1439
|
-
|
|
1440
|
-
|
|
1582
|
+
fs5.writeFileSync(resultsPath, header, "utf-8");
|
|
1583
|
+
logger6.debug("Initialized results.md", { sessionId });
|
|
1441
1584
|
}
|
|
1442
1585
|
/**
|
|
1443
1586
|
* Append a test step to the results.md file.
|
|
@@ -1445,7 +1588,7 @@ var StorageService = class {
|
|
|
1445
1588
|
*/
|
|
1446
1589
|
appendStepToResults(params) {
|
|
1447
1590
|
const { sessionId, step } = params;
|
|
1448
|
-
const
|
|
1591
|
+
const logger6 = getLogger();
|
|
1449
1592
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1450
1593
|
const resultsPath = path2.join(sessionDir, "results.md");
|
|
1451
1594
|
const statusIcon = step.success ? "\u2713" : "\u2717";
|
|
@@ -1457,8 +1600,8 @@ var StorageService = class {
|
|
|
1457
1600
|
step.screenshotPath ? `- **Screenshot:** [step-${String(step.stepNumber).padStart(3, "0")}.png](screenshots/step-${String(step.stepNumber).padStart(3, "0")}.png)` : "",
|
|
1458
1601
|
""
|
|
1459
1602
|
].filter(Boolean).join("\n");
|
|
1460
|
-
|
|
1461
|
-
|
|
1603
|
+
fs5.appendFileSync(resultsPath, stepContent + "\n", "utf-8");
|
|
1604
|
+
logger6.debug("Appended step to results", { sessionId, stepNumber: step.stepNumber });
|
|
1462
1605
|
const metadata = this.loadSessionMetadata(sessionId);
|
|
1463
1606
|
if (metadata) {
|
|
1464
1607
|
metadata.stepsCount = (metadata.stepsCount ?? 0) + 1;
|
|
@@ -1471,11 +1614,11 @@ var StorageService = class {
|
|
|
1471
1614
|
*/
|
|
1472
1615
|
finalizeResults(params) {
|
|
1473
1616
|
const { sessionId, status, summary } = params;
|
|
1474
|
-
const
|
|
1617
|
+
const logger6 = getLogger();
|
|
1475
1618
|
const sessionDir = path2.join(this.sessionsDir, sessionId);
|
|
1476
1619
|
const resultsPath = path2.join(sessionDir, "results.md");
|
|
1477
|
-
if (!
|
|
1478
|
-
|
|
1620
|
+
if (!fs5.existsSync(resultsPath)) {
|
|
1621
|
+
logger6.warn("Results file not found for finalization", { sessionId });
|
|
1479
1622
|
return;
|
|
1480
1623
|
}
|
|
1481
1624
|
const metadata = this.loadSessionMetadata(sessionId);
|
|
@@ -1497,8 +1640,8 @@ var StorageService = class {
|
|
|
1497
1640
|
"",
|
|
1498
1641
|
summary ? summary : ""
|
|
1499
1642
|
].join("\n");
|
|
1500
|
-
|
|
1501
|
-
|
|
1643
|
+
fs5.appendFileSync(resultsPath, footer, "utf-8");
|
|
1644
|
+
logger6.debug("Finalized results.md", { sessionId, status });
|
|
1502
1645
|
if (metadata) {
|
|
1503
1646
|
metadata.durationMs = durationMs;
|
|
1504
1647
|
metadata.endTime = endTime.toISOString();
|
|
@@ -1540,7 +1683,7 @@ var StorageService = class {
|
|
|
1540
1683
|
*/
|
|
1541
1684
|
cleanupOldSessions(params) {
|
|
1542
1685
|
const maxAgeDays = params?.maxAgeDays ?? DEFAULT_SESSION_MAX_AGE_DAYS;
|
|
1543
|
-
const
|
|
1686
|
+
const logger6 = getLogger();
|
|
1544
1687
|
const cutoffDate = /* @__PURE__ */ new Date();
|
|
1545
1688
|
cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
|
|
1546
1689
|
const sessionIds = this.listSessions();
|
|
@@ -1557,14 +1700,14 @@ var StorageService = class {
|
|
|
1557
1700
|
if (sessionDate < cutoffDate) {
|
|
1558
1701
|
const sessionDir = path2.join(this.sessionsDir, sessionId);
|
|
1559
1702
|
try {
|
|
1560
|
-
|
|
1703
|
+
fs5.rmSync(sessionDir, { recursive: true, force: true });
|
|
1561
1704
|
deletedCount++;
|
|
1562
|
-
|
|
1705
|
+
logger6.info("Deleted old session", {
|
|
1563
1706
|
sessionId,
|
|
1564
1707
|
age: Math.floor((Date.now() - sessionDate.getTime()) / (1e3 * 60 * 60 * 24))
|
|
1565
1708
|
});
|
|
1566
1709
|
} catch (error) {
|
|
1567
|
-
|
|
1710
|
+
logger6.error("Failed to delete session", {
|
|
1568
1711
|
sessionId,
|
|
1569
1712
|
error: error instanceof Error ? error.message : String(error)
|
|
1570
1713
|
});
|
|
@@ -1572,7 +1715,7 @@ var StorageService = class {
|
|
|
1572
1715
|
}
|
|
1573
1716
|
}
|
|
1574
1717
|
if (deletedCount > 0) {
|
|
1575
|
-
|
|
1718
|
+
logger6.info("Session cleanup completed", {
|
|
1576
1719
|
deletedCount,
|
|
1577
1720
|
maxAgeDays
|
|
1578
1721
|
});
|
|
@@ -1593,25 +1736,25 @@ var StorageService = class {
|
|
|
1593
1736
|
* @returns Whether deletion succeeded.
|
|
1594
1737
|
*/
|
|
1595
1738
|
deleteSession(sessionId) {
|
|
1596
|
-
const
|
|
1739
|
+
const logger6 = getLogger();
|
|
1597
1740
|
const sessionDir = path2.join(this.sessionsDir, sessionId);
|
|
1598
|
-
if (!
|
|
1599
|
-
|
|
1741
|
+
if (!fs5.existsSync(sessionDir)) {
|
|
1742
|
+
logger6.warn("Session not found for deletion", { sessionId });
|
|
1600
1743
|
return false;
|
|
1601
1744
|
}
|
|
1602
1745
|
try {
|
|
1603
1746
|
const currentId = this.getCurrentSessionId();
|
|
1604
1747
|
if (currentId === sessionId) {
|
|
1605
1748
|
const currentPath = path2.join(this.sessionsDir, "current");
|
|
1606
|
-
if (
|
|
1607
|
-
|
|
1749
|
+
if (fs5.existsSync(currentPath)) {
|
|
1750
|
+
fs5.unlinkSync(currentPath);
|
|
1608
1751
|
}
|
|
1609
1752
|
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1753
|
+
fs5.rmSync(sessionDir, { recursive: true, force: true });
|
|
1754
|
+
logger6.info("Deleted session", { sessionId });
|
|
1612
1755
|
return true;
|
|
1613
1756
|
} catch (error) {
|
|
1614
|
-
|
|
1757
|
+
logger6.error("Failed to delete session", {
|
|
1615
1758
|
sessionId,
|
|
1616
1759
|
error: error instanceof Error ? error.message : String(error)
|
|
1617
1760
|
});
|
|
@@ -1627,7 +1770,7 @@ function getStorageService() {
|
|
|
1627
1770
|
function resetStorageService() {
|
|
1628
1771
|
serviceInstance2 = null;
|
|
1629
1772
|
}
|
|
1630
|
-
var
|
|
1773
|
+
var logger3 = getLogger();
|
|
1631
1774
|
var RunResultStorageService = class {
|
|
1632
1775
|
/** Base directory for run results. */
|
|
1633
1776
|
runResultsDir;
|
|
@@ -1644,11 +1787,11 @@ var RunResultStorageService = class {
|
|
|
1644
1787
|
* Ensure storage directories exist.
|
|
1645
1788
|
*/
|
|
1646
1789
|
ensureDirectories() {
|
|
1647
|
-
if (!
|
|
1648
|
-
|
|
1790
|
+
if (!fs5.existsSync(this.runResultsDir)) {
|
|
1791
|
+
fs5.mkdirSync(this.runResultsDir, { recursive: true });
|
|
1649
1792
|
}
|
|
1650
|
-
if (!
|
|
1651
|
-
|
|
1793
|
+
if (!fs5.existsSync(this.testScriptsDir)) {
|
|
1794
|
+
fs5.mkdirSync(this.testScriptsDir, { recursive: true });
|
|
1652
1795
|
}
|
|
1653
1796
|
}
|
|
1654
1797
|
// ========================================
|
|
@@ -1659,14 +1802,14 @@ var RunResultStorageService = class {
|
|
|
1659
1802
|
*/
|
|
1660
1803
|
listRunResults() {
|
|
1661
1804
|
try {
|
|
1662
|
-
const files =
|
|
1805
|
+
const files = fs5.readdirSync(this.runResultsDir).filter((f) => f.endsWith(".json"));
|
|
1663
1806
|
const results = [];
|
|
1664
1807
|
for (const file of files) {
|
|
1665
1808
|
try {
|
|
1666
|
-
const content =
|
|
1809
|
+
const content = fs5.readFileSync(path2.join(this.runResultsDir, file), "utf-8");
|
|
1667
1810
|
results.push(JSON.parse(content));
|
|
1668
1811
|
} catch {
|
|
1669
|
-
|
|
1812
|
+
logger3.warn("Failed to read run result file", { file });
|
|
1670
1813
|
}
|
|
1671
1814
|
}
|
|
1672
1815
|
return results.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
@@ -1679,11 +1822,11 @@ var RunResultStorageService = class {
|
|
|
1679
1822
|
*/
|
|
1680
1823
|
getRunResult(runId) {
|
|
1681
1824
|
const filePath = path2.join(this.runResultsDir, `${runId}.json`);
|
|
1682
|
-
if (!
|
|
1825
|
+
if (!fs5.existsSync(filePath)) {
|
|
1683
1826
|
return void 0;
|
|
1684
1827
|
}
|
|
1685
1828
|
try {
|
|
1686
|
-
const content =
|
|
1829
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
1687
1830
|
return JSON.parse(content);
|
|
1688
1831
|
} catch {
|
|
1689
1832
|
return void 0;
|
|
@@ -1694,7 +1837,7 @@ var RunResultStorageService = class {
|
|
|
1694
1837
|
*/
|
|
1695
1838
|
saveRunResult(result) {
|
|
1696
1839
|
const filePath = path2.join(this.runResultsDir, `${result.id}.json`);
|
|
1697
|
-
|
|
1840
|
+
fs5.writeFileSync(filePath, JSON.stringify(result, null, 2));
|
|
1698
1841
|
}
|
|
1699
1842
|
/**
|
|
1700
1843
|
* Create a new run result.
|
|
@@ -1742,14 +1885,14 @@ var RunResultStorageService = class {
|
|
|
1742
1885
|
*/
|
|
1743
1886
|
listTestScripts() {
|
|
1744
1887
|
try {
|
|
1745
|
-
const files =
|
|
1888
|
+
const files = fs5.readdirSync(this.testScriptsDir).filter((f) => f.endsWith(".json"));
|
|
1746
1889
|
const scripts = [];
|
|
1747
1890
|
for (const file of files) {
|
|
1748
1891
|
try {
|
|
1749
|
-
const content =
|
|
1892
|
+
const content = fs5.readFileSync(path2.join(this.testScriptsDir, file), "utf-8");
|
|
1750
1893
|
scripts.push(JSON.parse(content));
|
|
1751
1894
|
} catch {
|
|
1752
|
-
|
|
1895
|
+
logger3.warn("Failed to read test script file", { file });
|
|
1753
1896
|
}
|
|
1754
1897
|
}
|
|
1755
1898
|
return scripts.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
@@ -1762,11 +1905,11 @@ var RunResultStorageService = class {
|
|
|
1762
1905
|
*/
|
|
1763
1906
|
getTestScript(testScriptId) {
|
|
1764
1907
|
const filePath = path2.join(this.testScriptsDir, `${testScriptId}.json`);
|
|
1765
|
-
if (!
|
|
1908
|
+
if (!fs5.existsSync(filePath)) {
|
|
1766
1909
|
return void 0;
|
|
1767
1910
|
}
|
|
1768
1911
|
try {
|
|
1769
|
-
const content =
|
|
1912
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
1770
1913
|
return JSON.parse(content);
|
|
1771
1914
|
} catch {
|
|
1772
1915
|
return void 0;
|
|
@@ -1777,7 +1920,7 @@ var RunResultStorageService = class {
|
|
|
1777
1920
|
*/
|
|
1778
1921
|
saveTestScript(script) {
|
|
1779
1922
|
const filePath = path2.join(this.testScriptsDir, `${script.id}.json`);
|
|
1780
|
-
|
|
1923
|
+
fs5.writeFileSync(filePath, JSON.stringify(script, null, 2));
|
|
1781
1924
|
}
|
|
1782
1925
|
/**
|
|
1783
1926
|
* Create a new test script.
|
|
@@ -1825,7 +1968,7 @@ function getRunResultStorageService() {
|
|
|
1825
1968
|
function resetRunResultStorageService() {
|
|
1826
1969
|
instance = null;
|
|
1827
1970
|
}
|
|
1828
|
-
var
|
|
1971
|
+
var logger4 = getLogger();
|
|
1829
1972
|
var activeProcesses = /* @__PURE__ */ new Map();
|
|
1830
1973
|
function getAuthenticatedUserId() {
|
|
1831
1974
|
const authService = getAuthService();
|
|
@@ -1848,7 +1991,7 @@ async function findPackageJsonAsync() {
|
|
|
1848
1991
|
];
|
|
1849
1992
|
for (const candidatePath of candidatePaths) {
|
|
1850
1993
|
try {
|
|
1851
|
-
const packageJsonRaw = await
|
|
1994
|
+
const packageJsonRaw = await fs7.readFile(candidatePath, "utf-8");
|
|
1852
1995
|
const packageJson = JSON.parse(packageJsonRaw);
|
|
1853
1996
|
if (packageJson.name === "@muggleai/works" && packageJson.version && packageJson.muggleConfig?.electronAppVersion) {
|
|
1854
1997
|
return {
|
|
@@ -1895,19 +2038,19 @@ function buildStudioAuthContent() {
|
|
|
1895
2038
|
async function ensureTempDir() {
|
|
1896
2039
|
const config = getConfig();
|
|
1897
2040
|
const tempDir = path2.join(config.localQa.dataDir, "temp");
|
|
1898
|
-
await
|
|
2041
|
+
await fs7.mkdir(tempDir, { recursive: true });
|
|
1899
2042
|
return tempDir;
|
|
1900
2043
|
}
|
|
1901
2044
|
async function writeTempFile(params) {
|
|
1902
2045
|
const tempDir = await ensureTempDir();
|
|
1903
2046
|
const filePath = path2.join(tempDir, params.filename);
|
|
1904
|
-
await
|
|
2047
|
+
await fs7.writeFile(filePath, JSON.stringify(params.data, null, 2));
|
|
1905
2048
|
return filePath;
|
|
1906
2049
|
}
|
|
1907
2050
|
async function cleanupTempFiles(params) {
|
|
1908
2051
|
for (const filePath of params.filePaths) {
|
|
1909
2052
|
try {
|
|
1910
|
-
await
|
|
2053
|
+
await fs7.unlink(filePath);
|
|
1911
2054
|
} catch {
|
|
1912
2055
|
}
|
|
1913
2056
|
}
|
|
@@ -1917,10 +2060,10 @@ async function moveResultsToArtifacts(params) {
|
|
|
1917
2060
|
const sessionsDir = storageService.getSessionsDir();
|
|
1918
2061
|
const artifactsDir = path2.join(sessionsDir, params.runId);
|
|
1919
2062
|
const actionScriptPath = path2.join(artifactsDir, "action-script.json");
|
|
1920
|
-
await
|
|
1921
|
-
await
|
|
2063
|
+
await fs7.mkdir(artifactsDir, { recursive: true });
|
|
2064
|
+
await fs7.copyFile(params.generatedScriptPath, actionScriptPath);
|
|
1922
2065
|
try {
|
|
1923
|
-
await
|
|
2066
|
+
await fs7.unlink(params.generatedScriptPath);
|
|
1924
2067
|
} catch {
|
|
1925
2068
|
}
|
|
1926
2069
|
return artifactsDir;
|
|
@@ -1929,14 +2072,14 @@ async function writeExecutionLogs(params) {
|
|
|
1929
2072
|
const storageService = getStorageService();
|
|
1930
2073
|
const sessionsDir = storageService.getSessionsDir();
|
|
1931
2074
|
const artifactsDir = path2.join(sessionsDir, params.runId);
|
|
1932
|
-
await
|
|
2075
|
+
await fs7.mkdir(artifactsDir, { recursive: true });
|
|
1933
2076
|
const stdoutPath = path2.join(artifactsDir, "stdout.log");
|
|
1934
2077
|
const stderrPath = path2.join(artifactsDir, "stderr.log");
|
|
1935
2078
|
await Promise.all([
|
|
1936
|
-
|
|
1937
|
-
|
|
2079
|
+
fs7.writeFile(stdoutPath, params.stdout, "utf-8"),
|
|
2080
|
+
fs7.writeFile(stderrPath, params.stderr, "utf-8")
|
|
1938
2081
|
]);
|
|
1939
|
-
|
|
2082
|
+
logger4.info("Wrote execution logs to artifacts directory", {
|
|
1940
2083
|
runId: params.runId,
|
|
1941
2084
|
artifactsDir
|
|
1942
2085
|
});
|
|
@@ -2068,7 +2211,7 @@ async function executeElectronAppAsync(params) {
|
|
|
2068
2211
|
if (params.showUi) {
|
|
2069
2212
|
spawnArgs.push("--show-ui");
|
|
2070
2213
|
}
|
|
2071
|
-
|
|
2214
|
+
logger4.info("Spawning electron-app for local execution", {
|
|
2072
2215
|
runId: params.runId,
|
|
2073
2216
|
mode,
|
|
2074
2217
|
electronAppPath,
|
|
@@ -2090,7 +2233,7 @@ async function executeElectronAppAsync(params) {
|
|
|
2090
2233
|
capturedStderr: ""
|
|
2091
2234
|
};
|
|
2092
2235
|
activeProcesses.set(params.runId, processInfo);
|
|
2093
|
-
return await new Promise((
|
|
2236
|
+
return await new Promise((resolve3, reject) => {
|
|
2094
2237
|
let settled = false;
|
|
2095
2238
|
const finalize = (result) => {
|
|
2096
2239
|
if (settled) {
|
|
@@ -2102,7 +2245,7 @@ async function executeElectronAppAsync(params) {
|
|
|
2102
2245
|
}
|
|
2103
2246
|
activeProcesses.delete(params.runId);
|
|
2104
2247
|
if (result.ok) {
|
|
2105
|
-
|
|
2248
|
+
resolve3(result.payload);
|
|
2106
2249
|
} else {
|
|
2107
2250
|
reject(result.payload);
|
|
2108
2251
|
}
|
|
@@ -2256,7 +2399,7 @@ ${executionResult.stderr}`;
|
|
|
2256
2399
|
path2.dirname(inputFilePath),
|
|
2257
2400
|
`gen_${path2.basename(inputFilePath)}`
|
|
2258
2401
|
);
|
|
2259
|
-
const generatedScriptRaw = await
|
|
2402
|
+
const generatedScriptRaw = await fs7.readFile(generatedScriptPath, "utf-8");
|
|
2260
2403
|
const generatedScript = JSON.parse(generatedScriptRaw);
|
|
2261
2404
|
const generatedSteps = generatedScript.steps;
|
|
2262
2405
|
if (!Array.isArray(generatedSteps)) {
|
|
@@ -2463,114 +2606,13 @@ function listActiveExecutions() {
|
|
|
2463
2606
|
status: process2.status
|
|
2464
2607
|
}));
|
|
2465
2608
|
}
|
|
2466
|
-
var API_KEY_FILE2 = "api-key.json";
|
|
2467
|
-
function getApiKeyFilePath() {
|
|
2468
|
-
return path2.join(getDataDir(), API_KEY_FILE2);
|
|
2469
|
-
}
|
|
2470
|
-
function ensureDataDir() {
|
|
2471
|
-
const dataDir = getDataDir();
|
|
2472
|
-
if (!fs3.existsSync(dataDir)) {
|
|
2473
|
-
fs3.mkdirSync(dataDir, { recursive: true });
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
function loadApiKeyData() {
|
|
2477
|
-
const logger14 = getLogger();
|
|
2478
|
-
const apiKeyPath = getApiKeyFilePath();
|
|
2479
|
-
try {
|
|
2480
|
-
if (!fs3.existsSync(apiKeyPath)) {
|
|
2481
|
-
logger14.debug("No API key file found", { path: apiKeyPath });
|
|
2482
|
-
return null;
|
|
2483
|
-
}
|
|
2484
|
-
const content = fs3.readFileSync(apiKeyPath, "utf-8");
|
|
2485
|
-
const data = JSON.parse(content);
|
|
2486
|
-
return data;
|
|
2487
|
-
} catch (error) {
|
|
2488
|
-
logger14.warn("Failed to load API key data", {
|
|
2489
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2490
|
-
});
|
|
2491
|
-
return null;
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
function saveApiKeyData(data) {
|
|
2495
|
-
const logger14 = getLogger();
|
|
2496
|
-
const apiKeyPath = getApiKeyFilePath();
|
|
2497
|
-
try {
|
|
2498
|
-
ensureDataDir();
|
|
2499
|
-
const content = JSON.stringify(data, null, 2);
|
|
2500
|
-
fs3.writeFileSync(apiKeyPath, content, { mode: 384 });
|
|
2501
|
-
logger14.info("API key saved", { path: apiKeyPath });
|
|
2502
|
-
} catch (error) {
|
|
2503
|
-
logger14.error("Failed to save API key", {
|
|
2504
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2505
|
-
});
|
|
2506
|
-
throw error;
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
function deleteApiKeyData() {
|
|
2510
|
-
const logger14 = getLogger();
|
|
2511
|
-
const apiKeyPath = getApiKeyFilePath();
|
|
2512
|
-
try {
|
|
2513
|
-
if (fs3.existsSync(apiKeyPath)) {
|
|
2514
|
-
fs3.unlinkSync(apiKeyPath);
|
|
2515
|
-
logger14.info("API key deleted", { path: apiKeyPath });
|
|
2516
|
-
}
|
|
2517
|
-
} catch (error) {
|
|
2518
|
-
logger14.warn("Failed to delete API key", {
|
|
2519
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2520
|
-
});
|
|
2521
|
-
}
|
|
2522
|
-
}
|
|
2523
|
-
function getValidApiKeyData() {
|
|
2524
|
-
const data = loadApiKeyData();
|
|
2525
|
-
if (!data) {
|
|
2526
|
-
return null;
|
|
2527
|
-
}
|
|
2528
|
-
if (data.apiKey) {
|
|
2529
|
-
return data;
|
|
2530
|
-
}
|
|
2531
|
-
return null;
|
|
2532
|
-
}
|
|
2533
|
-
function hasApiKey() {
|
|
2534
|
-
const data = loadApiKeyData();
|
|
2535
|
-
return !!data?.apiKey;
|
|
2536
|
-
}
|
|
2537
|
-
function getApiKey() {
|
|
2538
|
-
const data = loadApiKeyData();
|
|
2539
|
-
return data?.apiKey ?? null;
|
|
2540
|
-
}
|
|
2541
|
-
function saveApiKey(params) {
|
|
2542
|
-
const logger14 = getLogger();
|
|
2543
|
-
const apiKeyPath = getApiKeyFilePath();
|
|
2544
|
-
try {
|
|
2545
|
-
ensureDataDir();
|
|
2546
|
-
const data = {
|
|
2547
|
-
accessToken: "",
|
|
2548
|
-
expiresAt: "",
|
|
2549
|
-
apiKey: params.apiKey,
|
|
2550
|
-
apiKeyId: params.apiKeyId
|
|
2551
|
-
};
|
|
2552
|
-
const content = JSON.stringify(data, null, 2);
|
|
2553
|
-
fs3.writeFileSync(apiKeyPath, content, { mode: 384 });
|
|
2554
|
-
logger14.info("API key saved", { path: apiKeyPath });
|
|
2555
|
-
} catch (error) {
|
|
2556
|
-
logger14.error("Failed to save API key", {
|
|
2557
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2558
|
-
});
|
|
2559
|
-
throw error;
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2562
|
-
var loadCredentials = loadApiKeyData;
|
|
2563
|
-
var saveCredentials = saveApiKeyData;
|
|
2564
|
-
var deleteCredentials = deleteApiKeyData;
|
|
2565
|
-
var getValidCredentials = getValidApiKeyData;
|
|
2566
|
-
var getCredentialsFilePath = getApiKeyFilePath;
|
|
2567
2609
|
|
|
2568
2610
|
// packages/mcps/src/shared/auth.ts
|
|
2569
|
-
var
|
|
2611
|
+
var logger5 = getLogger();
|
|
2570
2612
|
async function startDeviceCodeFlow(config, options) {
|
|
2571
2613
|
const deviceCodeUrl = `https://${config.domain}/oauth/device/code`;
|
|
2572
2614
|
try {
|
|
2573
|
-
|
|
2615
|
+
logger5.info("[Auth] Starting device code flow", {
|
|
2574
2616
|
domain: config.domain,
|
|
2575
2617
|
clientId: config.clientId
|
|
2576
2618
|
});
|
|
@@ -2588,7 +2630,7 @@ async function startDeviceCodeFlow(config, options) {
|
|
|
2588
2630
|
}
|
|
2589
2631
|
);
|
|
2590
2632
|
const data = response.data;
|
|
2591
|
-
|
|
2633
|
+
logger5.info("[Auth] Device code flow started successfully", {
|
|
2592
2634
|
userCode: data.user_code,
|
|
2593
2635
|
expiresIn: data.expires_in
|
|
2594
2636
|
});
|
|
@@ -2598,15 +2640,15 @@ async function startDeviceCodeFlow(config, options) {
|
|
|
2598
2640
|
logoutUrl.searchParams.set("client_id", config.clientId);
|
|
2599
2641
|
logoutUrl.searchParams.set("returnTo", data.verification_uri_complete);
|
|
2600
2642
|
browserUrl = logoutUrl.toString();
|
|
2601
|
-
|
|
2643
|
+
logger5.info("[Auth] Force new session: opening logout-redirect URL");
|
|
2602
2644
|
}
|
|
2603
2645
|
const browserOpenResult = await openBrowserUrl({
|
|
2604
2646
|
url: browserUrl
|
|
2605
2647
|
});
|
|
2606
2648
|
if (browserOpenResult.opened) {
|
|
2607
|
-
|
|
2649
|
+
logger5.info("[Auth] Browser opened for device code login");
|
|
2608
2650
|
} else {
|
|
2609
|
-
|
|
2651
|
+
logger5.warn("[Auth] Failed to open browser", {
|
|
2610
2652
|
error: browserOpenResult.error,
|
|
2611
2653
|
verificationUriComplete: data.verification_uri_complete
|
|
2612
2654
|
});
|
|
@@ -2623,7 +2665,7 @@ async function startDeviceCodeFlow(config, options) {
|
|
|
2623
2665
|
};
|
|
2624
2666
|
} catch (error) {
|
|
2625
2667
|
if (error instanceof AxiosError) {
|
|
2626
|
-
|
|
2668
|
+
logger5.error("[Auth] Failed to start device code flow", {
|
|
2627
2669
|
status: error.response?.status,
|
|
2628
2670
|
data: error.response?.data
|
|
2629
2671
|
});
|
|
@@ -2651,7 +2693,7 @@ async function pollDeviceCode(config, deviceCode) {
|
|
|
2651
2693
|
}
|
|
2652
2694
|
}
|
|
2653
2695
|
);
|
|
2654
|
-
|
|
2696
|
+
logger5.info("[Auth] Authorization successful");
|
|
2655
2697
|
return {
|
|
2656
2698
|
status: "authorized",
|
|
2657
2699
|
accessToken: response.data.access_token,
|
|
@@ -2690,7 +2732,7 @@ async function pollDeviceCode(config, deviceCode) {
|
|
|
2690
2732
|
errorDescription: data.error_description || "User denied access"
|
|
2691
2733
|
};
|
|
2692
2734
|
}
|
|
2693
|
-
|
|
2735
|
+
logger5.error("[Auth] Unexpected error during poll", {
|
|
2694
2736
|
status: error.response.status,
|
|
2695
2737
|
error: errorCode,
|
|
2696
2738
|
description: data.error_description
|
|
@@ -2707,7 +2749,7 @@ async function createApiKeyWithToken(accessToken, keyName, expiry = "90d") {
|
|
|
2707
2749
|
const config = getConfig();
|
|
2708
2750
|
const apiKeyUrl = `${config.e2e.promptServiceBaseUrl}/v1/protected/api-keys`;
|
|
2709
2751
|
try {
|
|
2710
|
-
|
|
2752
|
+
logger5.info("[Auth] Creating API key", {
|
|
2711
2753
|
keyName,
|
|
2712
2754
|
expiry
|
|
2713
2755
|
});
|
|
@@ -2724,13 +2766,13 @@ async function createApiKeyWithToken(accessToken, keyName, expiry = "90d") {
|
|
|
2724
2766
|
}
|
|
2725
2767
|
}
|
|
2726
2768
|
);
|
|
2727
|
-
|
|
2769
|
+
logger5.info("[Auth] API key created successfully", {
|
|
2728
2770
|
keyId: response.data.id
|
|
2729
2771
|
});
|
|
2730
2772
|
return response.data;
|
|
2731
2773
|
} catch (error) {
|
|
2732
2774
|
if (error instanceof AxiosError) {
|
|
2733
|
-
|
|
2775
|
+
logger5.error("[Auth] Failed to create API key", {
|
|
2734
2776
|
status: error.response?.status,
|
|
2735
2777
|
data: error.response?.data
|
|
2736
2778
|
});
|
|
@@ -2770,7 +2812,7 @@ async function performLogin(keyName, keyExpiry = "90d", timeoutMs = 12e4) {
|
|
|
2770
2812
|
userId: storedAuth?.userId
|
|
2771
2813
|
};
|
|
2772
2814
|
if (keyName && storedAuth?.accessToken) {
|
|
2773
|
-
|
|
2815
|
+
logger5.info("[Auth] Creating API key as explicitly requested", {
|
|
2774
2816
|
keyName
|
|
2775
2817
|
});
|
|
2776
2818
|
const apiKeyResult = await createApiKeyWithToken(
|
|
@@ -2818,7 +2860,7 @@ function performLogout() {
|
|
|
2818
2860
|
const authService = getAuthService();
|
|
2819
2861
|
authService.logout();
|
|
2820
2862
|
deleteApiKeyData();
|
|
2821
|
-
|
|
2863
|
+
logger5.info("[Auth] Logged out successfully");
|
|
2822
2864
|
}
|
|
2823
2865
|
function getCallerCredentials() {
|
|
2824
2866
|
const apiKeyData = getValidApiKeyData();
|
|
@@ -2898,6 +2940,34 @@ function toolRequiresAuth(toolName) {
|
|
|
2898
2940
|
];
|
|
2899
2941
|
return !noAuthTools.includes(toolName);
|
|
2900
2942
|
}
|
|
2943
|
+
|
|
2944
|
+
// packages/mcps/src/mcp/index.ts
|
|
2945
|
+
var mcp_exports = {};
|
|
2946
|
+
__export(mcp_exports, {
|
|
2947
|
+
agents: () => agents_exports,
|
|
2948
|
+
e2e: () => e2e_exports2,
|
|
2949
|
+
localQa: () => local_exports2,
|
|
2950
|
+
plugins: () => plugins_exports,
|
|
2951
|
+
qa: () => e2e_exports2,
|
|
2952
|
+
skills: () => skills_exports,
|
|
2953
|
+
tools: () => tools_exports
|
|
2954
|
+
});
|
|
2955
|
+
|
|
2956
|
+
// packages/mcps/src/mcp/tools/index.ts
|
|
2957
|
+
var tools_exports = {};
|
|
2958
|
+
__export(tools_exports, {
|
|
2959
|
+
e2e: () => e2e_exports,
|
|
2960
|
+
localQa: () => local_exports,
|
|
2961
|
+
qa: () => e2e_exports
|
|
2962
|
+
});
|
|
2963
|
+
|
|
2964
|
+
// packages/mcps/src/mcp/tools/e2e/index.ts
|
|
2965
|
+
var e2e_exports = {};
|
|
2966
|
+
__export(e2e_exports, {
|
|
2967
|
+
allQaToolDefinitions: () => allQaToolDefinitions,
|
|
2968
|
+
executeQaTool: () => executeQaTool,
|
|
2969
|
+
getQaToolByName: () => getQaToolByName
|
|
2970
|
+
});
|
|
2901
2971
|
var MuggleEntityIdSchema = z.string().uuid();
|
|
2902
2972
|
var LocalExecutionContextInputSchema = z.object({
|
|
2903
2973
|
originalUrl: z.string().url().describe("Original local URL used during local execution (typically localhost)"),
|
|
@@ -3383,17 +3453,17 @@ var PromptServiceClient = class {
|
|
|
3383
3453
|
* @param path - Path to validate.
|
|
3384
3454
|
* @throws GatewayError if path is not allowed.
|
|
3385
3455
|
*/
|
|
3386
|
-
validatePath(
|
|
3387
|
-
const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) =>
|
|
3456
|
+
validatePath(path10) {
|
|
3457
|
+
const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) => path10.startsWith(prefix));
|
|
3388
3458
|
if (!isAllowed) {
|
|
3389
|
-
const
|
|
3390
|
-
|
|
3391
|
-
path:
|
|
3459
|
+
const logger6 = getLogger();
|
|
3460
|
+
logger6.error("Path not in allowlist", {
|
|
3461
|
+
path: path10,
|
|
3392
3462
|
allowedPrefixes: ALLOWED_UPSTREAM_PREFIXES
|
|
3393
3463
|
});
|
|
3394
3464
|
throw new GatewayError({
|
|
3395
3465
|
code: "FORBIDDEN" /* FORBIDDEN */,
|
|
3396
|
-
message: `Path '${
|
|
3466
|
+
message: `Path '${path10}' is not allowed`
|
|
3397
3467
|
});
|
|
3398
3468
|
}
|
|
3399
3469
|
}
|
|
@@ -3502,7 +3572,7 @@ var PromptServiceClient = class {
|
|
|
3502
3572
|
* @throws GatewayError on validation or upstream errors.
|
|
3503
3573
|
*/
|
|
3504
3574
|
async execute(call, credentials, correlationId) {
|
|
3505
|
-
const
|
|
3575
|
+
const logger6 = getLogger();
|
|
3506
3576
|
if (!credentials.bearerToken && !credentials.apiKey) {
|
|
3507
3577
|
throw new GatewayError({
|
|
3508
3578
|
code: "UNAUTHORIZED" /* UNAUTHORIZED */,
|
|
@@ -3514,7 +3584,7 @@ var PromptServiceClient = class {
|
|
|
3514
3584
|
const headers = this.buildHeaders(credentials, correlationId);
|
|
3515
3585
|
const timeout = call.timeoutMs || this.requestTimeoutMs;
|
|
3516
3586
|
const startTime = Date.now();
|
|
3517
|
-
|
|
3587
|
+
logger6.info("Upstream request", {
|
|
3518
3588
|
correlationId,
|
|
3519
3589
|
method: call.method,
|
|
3520
3590
|
path: call.path,
|
|
@@ -3541,7 +3611,7 @@ var PromptServiceClient = class {
|
|
|
3541
3611
|
}
|
|
3542
3612
|
const response = await this.httpClient.request(requestConfig);
|
|
3543
3613
|
const latency = Date.now() - startTime;
|
|
3544
|
-
|
|
3614
|
+
logger6.info("Upstream response", {
|
|
3545
3615
|
correlationId,
|
|
3546
3616
|
statusCode: response.status,
|
|
3547
3617
|
latencyMs: latency
|
|
@@ -3572,7 +3642,7 @@ var PromptServiceClient = class {
|
|
|
3572
3642
|
throw error;
|
|
3573
3643
|
}
|
|
3574
3644
|
if (error instanceof AxiosError) {
|
|
3575
|
-
|
|
3645
|
+
logger6.error("Upstream request failed", {
|
|
3576
3646
|
correlationId,
|
|
3577
3647
|
error: error.message,
|
|
3578
3648
|
code: error.code,
|
|
@@ -3591,7 +3661,7 @@ var PromptServiceClient = class {
|
|
|
3591
3661
|
details: { upstreamPath: call.path }
|
|
3592
3662
|
});
|
|
3593
3663
|
}
|
|
3594
|
-
|
|
3664
|
+
logger6.error("Unknown upstream error", {
|
|
3595
3665
|
correlationId,
|
|
3596
3666
|
error: String(error),
|
|
3597
3667
|
latencyMs: latency
|
|
@@ -5007,7 +5077,7 @@ function defaultResponseMapper(response) {
|
|
|
5007
5077
|
return response.data;
|
|
5008
5078
|
}
|
|
5009
5079
|
async function executeQaTool(toolName, input, correlationId) {
|
|
5010
|
-
const
|
|
5080
|
+
const logger6 = createChildLogger(correlationId);
|
|
5011
5081
|
const tool = getQaToolByName(toolName);
|
|
5012
5082
|
if (!tool) {
|
|
5013
5083
|
return {
|
|
@@ -5048,7 +5118,7 @@ async function executeQaTool(toolName, input, correlationId) {
|
|
|
5048
5118
|
}
|
|
5049
5119
|
} catch (error) {
|
|
5050
5120
|
if (error instanceof GatewayError) {
|
|
5051
|
-
|
|
5121
|
+
logger6.warn("Tool call failed with gateway error", {
|
|
5052
5122
|
tool: toolName,
|
|
5053
5123
|
code: error.code,
|
|
5054
5124
|
message: error.message
|
|
@@ -5059,7 +5129,7 @@ async function executeQaTool(toolName, input, correlationId) {
|
|
|
5059
5129
|
};
|
|
5060
5130
|
}
|
|
5061
5131
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5062
|
-
|
|
5132
|
+
logger6.error("Tool call failed", { tool: toolName, error: errorMessage });
|
|
5063
5133
|
return {
|
|
5064
5134
|
content: JSON.stringify({ error: "INTERNAL_ERROR", message: errorMessage }),
|
|
5065
5135
|
isError: true
|
|
@@ -5067,82 +5137,12 @@ async function executeQaTool(toolName, input, correlationId) {
|
|
|
5067
5137
|
}
|
|
5068
5138
|
}
|
|
5069
5139
|
|
|
5070
|
-
// packages/mcps/src/mcp/tools/
|
|
5071
|
-
var
|
|
5072
|
-
__export(
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
});
|
|
5077
|
-
|
|
5078
|
-
// packages/mcps/src/mcp/e2e/index.ts
|
|
5079
|
-
function getQaTools() {
|
|
5080
|
-
return allQaToolDefinitions.map((tool) => ({
|
|
5081
|
-
name: tool.name,
|
|
5082
|
-
description: tool.description,
|
|
5083
|
-
inputSchema: tool.inputSchema,
|
|
5084
|
-
requiresAuth: tool.requiresAuth !== false,
|
|
5085
|
-
execute: async (params) => {
|
|
5086
|
-
return executeQaTool(tool.name, params.input, params.correlationId);
|
|
5087
|
-
}
|
|
5088
|
-
}));
|
|
5089
|
-
}
|
|
5090
|
-
|
|
5091
|
-
// packages/mcps/src/mcp/local/index.ts
|
|
5092
|
-
var local_exports2 = {};
|
|
5093
|
-
__export(local_exports2, {
|
|
5094
|
-
AuthLoginInputSchema: () => AuthLoginInputSchema2,
|
|
5095
|
-
AuthPollInputSchema: () => AuthPollInputSchema2,
|
|
5096
|
-
AuthService: () => AuthService,
|
|
5097
|
-
CancelExecutionInputSchema: () => CancelExecutionInputSchema,
|
|
5098
|
-
CleanupSessionsInputSchema: () => CleanupSessionsInputSchema,
|
|
5099
|
-
CloudMappingEntityType: () => CloudMappingEntityType,
|
|
5100
|
-
DeviceCodePollStatus: () => DeviceCodePollStatus,
|
|
5101
|
-
EmptyInputSchema: () => EmptyInputSchema2,
|
|
5102
|
-
ExecuteReplayInputSchema: () => ExecuteReplayInputSchema,
|
|
5103
|
-
ExecuteTestGenerationInputSchema: () => ExecuteTestGenerationInputSchema,
|
|
5104
|
-
ExecutionStatus: () => ExecutionStatus,
|
|
5105
|
-
HealthStatus: () => HealthStatus,
|
|
5106
|
-
ListSessionsInputSchema: () => ListSessionsInputSchema,
|
|
5107
|
-
LocalRunStatus: () => LocalRunStatus,
|
|
5108
|
-
LocalRunType: () => LocalRunType,
|
|
5109
|
-
LocalTestScriptStatus: () => LocalTestScriptStatus,
|
|
5110
|
-
LocalWorkflowFileEntityType: () => LocalWorkflowFileEntityType,
|
|
5111
|
-
LocalWorkflowRunStatus: () => LocalWorkflowRunStatus,
|
|
5112
|
-
MuggleEntityIdSchema: () => MuggleEntityIdSchema,
|
|
5113
|
-
PublishTestScriptInputSchema: () => PublishTestScriptInputSchema,
|
|
5114
|
-
RunResultGetInputSchema: () => RunResultGetInputSchema,
|
|
5115
|
-
RunResultListInputSchema: () => RunResultListInputSchema,
|
|
5116
|
-
RunResultStorageService: () => RunResultStorageService,
|
|
5117
|
-
SessionStatus: () => SessionStatus,
|
|
5118
|
-
StorageService: () => StorageService,
|
|
5119
|
-
TestCaseDetailsSchema: () => TestCaseDetailsSchema,
|
|
5120
|
-
TestResultStatus: () => TestResultStatus,
|
|
5121
|
-
TestScriptDetailsSchema: () => TestScriptDetailsSchema,
|
|
5122
|
-
TestScriptGetInputSchema: () => TestScriptGetInputSchema2,
|
|
5123
|
-
TestScriptListInputSchema: () => TestScriptListInputSchema2,
|
|
5124
|
-
allLocalQaTools: () => allLocalQaTools,
|
|
5125
|
-
cancelExecution: () => cancelExecution,
|
|
5126
|
-
executeReplay: () => executeReplay,
|
|
5127
|
-
executeTestGeneration: () => executeTestGeneration,
|
|
5128
|
-
executeTool: () => executeTool,
|
|
5129
|
-
getAuthService: () => getAuthService,
|
|
5130
|
-
getLocalQaTools: () => getLocalQaTools,
|
|
5131
|
-
getRunResultStorageService: () => getRunResultStorageService,
|
|
5132
|
-
getStorageService: () => getStorageService,
|
|
5133
|
-
getTool: () => getTool,
|
|
5134
|
-
listActiveExecutions: () => listActiveExecutions,
|
|
5135
|
-
resetAuthService: () => resetAuthService,
|
|
5136
|
-
resetRunResultStorageService: () => resetRunResultStorageService,
|
|
5137
|
-
resetStorageService: () => resetStorageService
|
|
5138
|
-
});
|
|
5139
|
-
|
|
5140
|
-
// packages/mcps/src/mcp/tools/local/index.ts
|
|
5141
|
-
var local_exports = {};
|
|
5142
|
-
__export(local_exports, {
|
|
5143
|
-
allLocalQaTools: () => allLocalQaTools,
|
|
5144
|
-
executeTool: () => executeTool,
|
|
5145
|
-
getTool: () => getTool
|
|
5140
|
+
// packages/mcps/src/mcp/tools/local/index.ts
|
|
5141
|
+
var local_exports = {};
|
|
5142
|
+
__export(local_exports, {
|
|
5143
|
+
allLocalQaTools: () => allLocalQaTools,
|
|
5144
|
+
executeTool: () => executeTool,
|
|
5145
|
+
getTool: () => getTool
|
|
5146
5146
|
});
|
|
5147
5147
|
var AuthLoginInputSchema2 = z.object({
|
|
5148
5148
|
waitForCompletion: z.boolean().optional().describe("Whether to wait for browser login completion before returning. Default: true"),
|
|
@@ -5241,12 +5241,12 @@ var CleanupSessionsInputSchema = z.object({
|
|
|
5241
5241
|
|
|
5242
5242
|
// packages/mcps/src/mcp/tools/local/tool-registry.ts
|
|
5243
5243
|
function createChildLogger2(correlationId) {
|
|
5244
|
-
const
|
|
5244
|
+
const logger6 = getLogger();
|
|
5245
5245
|
return {
|
|
5246
|
-
info: (msg, meta) =>
|
|
5247
|
-
error: (msg, meta) =>
|
|
5248
|
-
warn: (msg, meta) =>
|
|
5249
|
-
debug: (msg, meta) =>
|
|
5246
|
+
info: (msg, meta) => logger6.info(msg, { ...meta, correlationId }),
|
|
5247
|
+
error: (msg, meta) => logger6.error(msg, { ...meta, correlationId }),
|
|
5248
|
+
warn: (msg, meta) => logger6.warn(msg, { ...meta, correlationId }),
|
|
5249
|
+
debug: (msg, meta) => logger6.debug(msg, { ...meta, correlationId })
|
|
5250
5250
|
};
|
|
5251
5251
|
}
|
|
5252
5252
|
var checkStatusTool = {
|
|
@@ -5254,8 +5254,8 @@ var checkStatusTool = {
|
|
|
5254
5254
|
description: "Check the status of Muggle Test Local. This verifies the connection to web-service and shows current session information.",
|
|
5255
5255
|
inputSchema: EmptyInputSchema2,
|
|
5256
5256
|
execute: async (ctx) => {
|
|
5257
|
-
const
|
|
5258
|
-
|
|
5257
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5258
|
+
logger6.info("Executing muggle-local-check-status");
|
|
5259
5259
|
const authService = getAuthService();
|
|
5260
5260
|
const storageService = getStorageService();
|
|
5261
5261
|
const authStatus = authService.getAuthStatus();
|
|
@@ -5278,8 +5278,8 @@ var listSessionsTool = {
|
|
|
5278
5278
|
description: "List all stored testing sessions. Shows session IDs, status, and metadata for each session.",
|
|
5279
5279
|
inputSchema: ListSessionsInputSchema,
|
|
5280
5280
|
execute: async (ctx) => {
|
|
5281
|
-
const
|
|
5282
|
-
|
|
5281
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5282
|
+
logger6.info("Executing muggle-local-list-sessions");
|
|
5283
5283
|
const input = ListSessionsInputSchema.parse(ctx.input);
|
|
5284
5284
|
const storageService = getStorageService();
|
|
5285
5285
|
const sessions = storageService.listSessionsWithMetadata();
|
|
@@ -5306,8 +5306,8 @@ var runResultListTool = {
|
|
|
5306
5306
|
description: "List run results (test generation and replay history), optionally filtered by cloud test case ID.",
|
|
5307
5307
|
inputSchema: RunResultListInputSchema,
|
|
5308
5308
|
execute: async (ctx) => {
|
|
5309
|
-
const
|
|
5310
|
-
|
|
5309
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5310
|
+
logger6.info("Executing muggle-local-run-result-list");
|
|
5311
5311
|
const input = RunResultListInputSchema.parse(ctx.input);
|
|
5312
5312
|
const storage = getRunResultStorageService();
|
|
5313
5313
|
let results = storage.listRunResults();
|
|
@@ -5331,8 +5331,8 @@ var runResultGetTool = {
|
|
|
5331
5331
|
description: "Get detailed information about a run result including screenshots and action script output.",
|
|
5332
5332
|
inputSchema: RunResultGetInputSchema,
|
|
5333
5333
|
execute: async (ctx) => {
|
|
5334
|
-
const
|
|
5335
|
-
|
|
5334
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5335
|
+
logger6.info("Executing muggle-local-run-result-get");
|
|
5336
5336
|
const input = RunResultGetInputSchema.parse(ctx.input);
|
|
5337
5337
|
const storage = getRunResultStorageService();
|
|
5338
5338
|
const result = storage.getRunResult(input.runId);
|
|
@@ -5354,7 +5354,7 @@ var runResultGetTool = {
|
|
|
5354
5354
|
const testScript = storage.getTestScript(result.testScriptId);
|
|
5355
5355
|
testScriptSteps = testScript?.actionScript?.length;
|
|
5356
5356
|
}
|
|
5357
|
-
if (result.artifactsDir &&
|
|
5357
|
+
if (result.artifactsDir && fs5.existsSync(result.artifactsDir)) {
|
|
5358
5358
|
contentParts.push(
|
|
5359
5359
|
"",
|
|
5360
5360
|
"### Artifacts (view action script + screenshots)",
|
|
@@ -5368,20 +5368,20 @@ var runResultGetTool = {
|
|
|
5368
5368
|
const stdoutLogPath = path2.join(result.artifactsDir, "stdout.log");
|
|
5369
5369
|
const stderrLogPath = path2.join(result.artifactsDir, "stderr.log");
|
|
5370
5370
|
const artifactItems = [];
|
|
5371
|
-
if (
|
|
5371
|
+
if (fs5.existsSync(actionScriptPath)) {
|
|
5372
5372
|
artifactItems.push("- `action-script.json` \u2014 generated test steps");
|
|
5373
5373
|
}
|
|
5374
|
-
if (
|
|
5374
|
+
if (fs5.existsSync(resultsMdPath)) {
|
|
5375
5375
|
artifactItems.push("- `results.md` \u2014 step-by-step report with screenshot links");
|
|
5376
5376
|
}
|
|
5377
|
-
if (
|
|
5378
|
-
const screenshots =
|
|
5377
|
+
if (fs5.existsSync(screenshotsDir)) {
|
|
5378
|
+
const screenshots = fs5.readdirSync(screenshotsDir).filter((f) => /\.(png|jpg|jpeg|webp)$/i.test(f));
|
|
5379
5379
|
artifactItems.push(`- \`screenshots/\` \u2014 ${screenshots.length} image(s)`);
|
|
5380
5380
|
}
|
|
5381
|
-
if (
|
|
5381
|
+
if (fs5.existsSync(stdoutLogPath)) {
|
|
5382
5382
|
artifactItems.push("- `stdout.log` \u2014 electron-app stdout output");
|
|
5383
5383
|
}
|
|
5384
|
-
if (
|
|
5384
|
+
if (fs5.existsSync(stderrLogPath)) {
|
|
5385
5385
|
artifactItems.push("- `stderr.log` \u2014 electron-app stderr output");
|
|
5386
5386
|
}
|
|
5387
5387
|
if (artifactItems.length > 0) {
|
|
@@ -5406,8 +5406,8 @@ var testScriptListTool = {
|
|
|
5406
5406
|
description: "List locally generated test scripts, optionally filtered by cloud test case ID.",
|
|
5407
5407
|
inputSchema: TestScriptListInputSchema2,
|
|
5408
5408
|
execute: async (ctx) => {
|
|
5409
|
-
const
|
|
5410
|
-
|
|
5409
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5410
|
+
logger6.info("Executing muggle-local-test-script-list");
|
|
5411
5411
|
const input = TestScriptListInputSchema2.parse(ctx.input);
|
|
5412
5412
|
const storage = getRunResultStorageService();
|
|
5413
5413
|
let scripts = storage.listTestScripts();
|
|
@@ -5427,8 +5427,8 @@ var testScriptGetTool = {
|
|
|
5427
5427
|
description: "Get details of a locally generated test script including action script steps.",
|
|
5428
5428
|
inputSchema: TestScriptGetInputSchema2,
|
|
5429
5429
|
execute: async (ctx) => {
|
|
5430
|
-
const
|
|
5431
|
-
|
|
5430
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5431
|
+
logger6.info("Executing muggle-local-test-script-get");
|
|
5432
5432
|
const input = TestScriptGetInputSchema2.parse(ctx.input);
|
|
5433
5433
|
const storage = getRunResultStorageService();
|
|
5434
5434
|
const testScript = storage.getTestScript(input.testScriptId);
|
|
@@ -5453,8 +5453,8 @@ var executeTestGenerationTool = {
|
|
|
5453
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.",
|
|
5454
5454
|
inputSchema: ExecuteTestGenerationInputSchema,
|
|
5455
5455
|
execute: async (ctx) => {
|
|
5456
|
-
const
|
|
5457
|
-
|
|
5456
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5457
|
+
logger6.info("Executing muggle-local-execute-test-generation");
|
|
5458
5458
|
const input = ExecuteTestGenerationInputSchema.parse(ctx.input);
|
|
5459
5459
|
const showUi = input.showUi !== false;
|
|
5460
5460
|
try {
|
|
@@ -5481,7 +5481,7 @@ var executeTestGenerationTool = {
|
|
|
5481
5481
|
};
|
|
5482
5482
|
} catch (error) {
|
|
5483
5483
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5484
|
-
|
|
5484
|
+
logger6.error("Test generation failed", { error: errorMessage });
|
|
5485
5485
|
return { content: `Test generation failed: ${errorMessage}`, isError: true };
|
|
5486
5486
|
}
|
|
5487
5487
|
}
|
|
@@ -5491,8 +5491,8 @@ var executeReplayTool = {
|
|
|
5491
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.",
|
|
5492
5492
|
inputSchema: ExecuteReplayInputSchema,
|
|
5493
5493
|
execute: async (ctx) => {
|
|
5494
|
-
const
|
|
5495
|
-
|
|
5494
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5495
|
+
logger6.info("Executing muggle-local-execute-replay");
|
|
5496
5496
|
const input = ExecuteReplayInputSchema.parse(ctx.input);
|
|
5497
5497
|
const showUi = input.showUi !== false;
|
|
5498
5498
|
try {
|
|
@@ -5520,7 +5520,7 @@ var executeReplayTool = {
|
|
|
5520
5520
|
};
|
|
5521
5521
|
} catch (error) {
|
|
5522
5522
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5523
|
-
|
|
5523
|
+
logger6.error("Test replay failed", { error: errorMessage });
|
|
5524
5524
|
return { content: `Test replay failed: ${errorMessage}`, isError: true };
|
|
5525
5525
|
}
|
|
5526
5526
|
}
|
|
@@ -5530,8 +5530,8 @@ var cancelExecutionTool = {
|
|
|
5530
5530
|
description: "Cancel an active test generation or replay execution.",
|
|
5531
5531
|
inputSchema: CancelExecutionInputSchema,
|
|
5532
5532
|
execute: async (ctx) => {
|
|
5533
|
-
const
|
|
5534
|
-
|
|
5533
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5534
|
+
logger6.info("Executing muggle-local-cancel-execution");
|
|
5535
5535
|
const input = CancelExecutionInputSchema.parse(ctx.input);
|
|
5536
5536
|
const cancelled = cancelExecution({ runId: input.runId });
|
|
5537
5537
|
if (cancelled) {
|
|
@@ -5545,8 +5545,8 @@ var publishTestScriptTool = {
|
|
|
5545
5545
|
description: "Publish a locally generated test script to the cloud. Uses the run ID from muggle_execute_test_generation to find the script and uploads it to the specified cloud test case. Returns a viewUrl that can be opened in the user's browser to view the published test script on the dashboard.",
|
|
5546
5546
|
inputSchema: PublishTestScriptInputSchema,
|
|
5547
5547
|
execute: async (ctx) => {
|
|
5548
|
-
const
|
|
5549
|
-
|
|
5548
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5549
|
+
logger6.info("Executing muggle-local-publish-test-script");
|
|
5550
5550
|
const input = PublishTestScriptInputSchema.parse(ctx.input);
|
|
5551
5551
|
const storage = getRunResultStorageService();
|
|
5552
5552
|
const runResult = storage.getRunResult(input.runId);
|
|
@@ -5665,7 +5665,7 @@ var publishTestScriptTool = {
|
|
|
5665
5665
|
};
|
|
5666
5666
|
} catch (error) {
|
|
5667
5667
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5668
|
-
|
|
5668
|
+
logger6.error("Failed to publish local test script to cloud", {
|
|
5669
5669
|
runId: input.runId,
|
|
5670
5670
|
cloudTestCaseId: input.cloudTestCaseId,
|
|
5671
5671
|
error: errorMessage
|
|
@@ -5711,7 +5711,161 @@ async function executeTool(name, input, correlationId) {
|
|
|
5711
5711
|
return tool.execute({ input, correlationId });
|
|
5712
5712
|
}
|
|
5713
5713
|
|
|
5714
|
+
// packages/mcps/src/mcp/e2e/index.ts
|
|
5715
|
+
var e2e_exports2 = {};
|
|
5716
|
+
__export(e2e_exports2, {
|
|
5717
|
+
ActionScriptGetInputSchema: () => ActionScriptGetInputSchema,
|
|
5718
|
+
ApiKeyCreateInputSchema: () => ApiKeyCreateInputSchema,
|
|
5719
|
+
ApiKeyGetInputSchema: () => ApiKeyGetInputSchema,
|
|
5720
|
+
ApiKeyListInputSchema: () => ApiKeyListInputSchema,
|
|
5721
|
+
ApiKeyRecordIdSchema: () => ApiKeyRecordIdSchema,
|
|
5722
|
+
ApiKeyRevokeInputSchema: () => ApiKeyRevokeInputSchema,
|
|
5723
|
+
AuthLoginInputSchema: () => AuthLoginInputSchema,
|
|
5724
|
+
AuthPollInputSchema: () => AuthPollInputSchema,
|
|
5725
|
+
BulkPreviewJobCancelInputSchema: () => BulkPreviewJobCancelInputSchema,
|
|
5726
|
+
BulkPreviewJobGetInputSchema: () => BulkPreviewJobGetInputSchema,
|
|
5727
|
+
BulkPreviewJobKindSchema: () => BulkPreviewJobKindSchema,
|
|
5728
|
+
BulkPreviewJobListInputSchema: () => BulkPreviewJobListInputSchema,
|
|
5729
|
+
BulkPreviewJobStatusSchema: () => BulkPreviewJobStatusSchema,
|
|
5730
|
+
BulkPreviewPromptSchema: () => BulkPreviewPromptSchema,
|
|
5731
|
+
BulkPreviewSubmitTestCaseInputSchema: () => BulkPreviewSubmitTestCaseInputSchema,
|
|
5732
|
+
BulkPreviewSubmitUseCaseInputSchema: () => BulkPreviewSubmitUseCaseInputSchema,
|
|
5733
|
+
EmptyInputSchema: () => EmptyInputSchema,
|
|
5734
|
+
GatewayError: () => GatewayError,
|
|
5735
|
+
IdSchema: () => IdSchema,
|
|
5736
|
+
LocalExecutionContextInputSchema: () => LocalExecutionContextInputSchema,
|
|
5737
|
+
LocalRunUploadInputSchema: () => LocalRunUploadInputSchema,
|
|
5738
|
+
McpErrorCode: () => McpErrorCode,
|
|
5739
|
+
MuggleEntityIdSchema: () => MuggleEntityIdSchema,
|
|
5740
|
+
PaginationInputSchema: () => PaginationInputSchema,
|
|
5741
|
+
PrdFileDeleteInputSchema: () => PrdFileDeleteInputSchema,
|
|
5742
|
+
PrdFileListInputSchema: () => PrdFileListInputSchema,
|
|
5743
|
+
PrdFileProcessLatestRunInputSchema: () => PrdFileProcessLatestRunInputSchema,
|
|
5744
|
+
PrdFileProcessStartInputSchema: () => PrdFileProcessStartInputSchema,
|
|
5745
|
+
PrdFileUploadInputSchema: () => PrdFileUploadInputSchema,
|
|
5746
|
+
ProjectCreateInputSchema: () => ProjectCreateInputSchema,
|
|
5747
|
+
ProjectDeleteInputSchema: () => ProjectDeleteInputSchema,
|
|
5748
|
+
ProjectGetInputSchema: () => ProjectGetInputSchema,
|
|
5749
|
+
ProjectListInputSchema: () => ProjectListInputSchema,
|
|
5750
|
+
ProjectTestResultsSummaryInputSchema: () => ProjectTestResultsSummaryInputSchema,
|
|
5751
|
+
ProjectTestRunsSummaryInputSchema: () => ProjectTestRunsSummaryInputSchema,
|
|
5752
|
+
ProjectTestScriptsSummaryInputSchema: () => ProjectTestScriptsSummaryInputSchema,
|
|
5753
|
+
ProjectUpdateInputSchema: () => ProjectUpdateInputSchema,
|
|
5754
|
+
PromptServiceClient: () => PromptServiceClient,
|
|
5755
|
+
RecommendCicdSetupInputSchema: () => RecommendCicdSetupInputSchema,
|
|
5756
|
+
RecommendScheduleInputSchema: () => RecommendScheduleInputSchema,
|
|
5757
|
+
ReportCostQueryInputSchema: () => ReportCostQueryInputSchema,
|
|
5758
|
+
ReportFinalGenerateInputSchema: () => ReportFinalGenerateInputSchema,
|
|
5759
|
+
ReportPreferencesUpsertInputSchema: () => ReportPreferencesUpsertInputSchema,
|
|
5760
|
+
ReportStatsSummaryInputSchema: () => ReportStatsSummaryInputSchema,
|
|
5761
|
+
RunBatchIdSchema: () => RunBatchIdSchema,
|
|
5762
|
+
SecretCreateInputSchema: () => SecretCreateInputSchema,
|
|
5763
|
+
SecretDeleteInputSchema: () => SecretDeleteInputSchema,
|
|
5764
|
+
SecretGetInputSchema: () => SecretGetInputSchema,
|
|
5765
|
+
SecretListInputSchema: () => SecretListInputSchema,
|
|
5766
|
+
SecretUpdateInputSchema: () => SecretUpdateInputSchema,
|
|
5767
|
+
StripePaymentMethodIdSchema: () => StripePaymentMethodIdSchema,
|
|
5768
|
+
TestCaseCreateInputSchema: () => TestCaseCreateInputSchema,
|
|
5769
|
+
TestCaseGenerateFromPromptInputSchema: () => TestCaseGenerateFromPromptInputSchema,
|
|
5770
|
+
TestCaseGetInputSchema: () => TestCaseGetInputSchema,
|
|
5771
|
+
TestCaseListByUseCaseInputSchema: () => TestCaseListByUseCaseInputSchema,
|
|
5772
|
+
TestCaseListInputSchema: () => TestCaseListInputSchema,
|
|
5773
|
+
TestScriptGetInputSchema: () => TestScriptGetInputSchema,
|
|
5774
|
+
TestScriptListInputSchema: () => TestScriptListInputSchema,
|
|
5775
|
+
TokenPackageIdSchema: () => TokenPackageIdSchema,
|
|
5776
|
+
TokenUsageFilterTypeSchema: () => TokenUsageFilterTypeSchema,
|
|
5777
|
+
UseCaseCandidatesApproveInputSchema: () => UseCaseCandidatesApproveInputSchema,
|
|
5778
|
+
UseCaseCreateFromPromptsInputSchema: () => UseCaseCreateFromPromptsInputSchema,
|
|
5779
|
+
UseCaseCreateInputSchema: () => UseCaseCreateInputSchema,
|
|
5780
|
+
UseCaseDiscoveryMemoryGetInputSchema: () => UseCaseDiscoveryMemoryGetInputSchema,
|
|
5781
|
+
UseCaseGetInputSchema: () => UseCaseGetInputSchema,
|
|
5782
|
+
UseCaseListInputSchema: () => UseCaseListInputSchema,
|
|
5783
|
+
UseCasePromptPreviewInputSchema: () => UseCasePromptPreviewInputSchema,
|
|
5784
|
+
UseCaseUpdateFromPromptInputSchema: () => UseCaseUpdateFromPromptInputSchema,
|
|
5785
|
+
WalletAutoTopUpSetPaymentMethodInputSchema: () => WalletAutoTopUpSetPaymentMethodInputSchema,
|
|
5786
|
+
WalletAutoTopUpUpdateInputSchema: () => WalletAutoTopUpUpdateInputSchema,
|
|
5787
|
+
WalletPaymentMethodCreateSetupSessionInputSchema: () => WalletPaymentMethodCreateSetupSessionInputSchema,
|
|
5788
|
+
WalletPaymentMethodListInputSchema: () => WalletPaymentMethodListInputSchema,
|
|
5789
|
+
WalletTopUpInputSchema: () => WalletTopUpInputSchema,
|
|
5790
|
+
WorkflowCancelRunInputSchema: () => WorkflowCancelRunInputSchema,
|
|
5791
|
+
WorkflowCancelRuntimeInputSchema: () => WorkflowCancelRuntimeInputSchema,
|
|
5792
|
+
WorkflowGetLatestRunInputSchema: () => WorkflowGetLatestRunInputSchema,
|
|
5793
|
+
WorkflowGetLatestScriptGenByTestCaseInputSchema: () => WorkflowGetLatestScriptGenByTestCaseInputSchema,
|
|
5794
|
+
WorkflowGetReplayBulkBatchSummaryInputSchema: () => WorkflowGetReplayBulkBatchSummaryInputSchema,
|
|
5795
|
+
WorkflowListRuntimesInputSchema: () => WorkflowListRuntimesInputSchema,
|
|
5796
|
+
WorkflowMemoryParamsSchema: () => WorkflowMemoryParamsSchema,
|
|
5797
|
+
WorkflowParamsSchema: () => WorkflowParamsSchema,
|
|
5798
|
+
WorkflowStartTestCaseDetectionInputSchema: () => WorkflowStartTestCaseDetectionInputSchema,
|
|
5799
|
+
WorkflowStartTestScriptGenerationInputSchema: () => WorkflowStartTestScriptGenerationInputSchema,
|
|
5800
|
+
WorkflowStartTestScriptReplayBulkInputSchema: () => WorkflowStartTestScriptReplayBulkInputSchema,
|
|
5801
|
+
WorkflowStartTestScriptReplayInputSchema: () => WorkflowStartTestScriptReplayInputSchema,
|
|
5802
|
+
WorkflowStartWebsiteScanInputSchema: () => WorkflowStartWebsiteScanInputSchema,
|
|
5803
|
+
allQaToolDefinitions: () => allQaToolDefinitions,
|
|
5804
|
+
executeQaTool: () => executeQaTool,
|
|
5805
|
+
getPromptServiceClient: () => getPromptServiceClient,
|
|
5806
|
+
getQaToolByName: () => getQaToolByName,
|
|
5807
|
+
getQaTools: () => getQaTools
|
|
5808
|
+
});
|
|
5809
|
+
function getQaTools() {
|
|
5810
|
+
return allQaToolDefinitions.map((tool) => ({
|
|
5811
|
+
name: tool.name,
|
|
5812
|
+
description: tool.description,
|
|
5813
|
+
inputSchema: tool.inputSchema,
|
|
5814
|
+
requiresAuth: tool.requiresAuth !== false,
|
|
5815
|
+
execute: async (params) => {
|
|
5816
|
+
return executeQaTool(tool.name, params.input, params.correlationId);
|
|
5817
|
+
}
|
|
5818
|
+
}));
|
|
5819
|
+
}
|
|
5820
|
+
|
|
5714
5821
|
// packages/mcps/src/mcp/local/index.ts
|
|
5822
|
+
var local_exports2 = {};
|
|
5823
|
+
__export(local_exports2, {
|
|
5824
|
+
AuthLoginInputSchema: () => AuthLoginInputSchema2,
|
|
5825
|
+
AuthPollInputSchema: () => AuthPollInputSchema2,
|
|
5826
|
+
AuthService: () => AuthService,
|
|
5827
|
+
CancelExecutionInputSchema: () => CancelExecutionInputSchema,
|
|
5828
|
+
CleanupSessionsInputSchema: () => CleanupSessionsInputSchema,
|
|
5829
|
+
CloudMappingEntityType: () => CloudMappingEntityType,
|
|
5830
|
+
DeviceCodePollStatus: () => DeviceCodePollStatus,
|
|
5831
|
+
EmptyInputSchema: () => EmptyInputSchema2,
|
|
5832
|
+
ExecuteReplayInputSchema: () => ExecuteReplayInputSchema,
|
|
5833
|
+
ExecuteTestGenerationInputSchema: () => ExecuteTestGenerationInputSchema,
|
|
5834
|
+
ExecutionStatus: () => ExecutionStatus,
|
|
5835
|
+
HealthStatus: () => HealthStatus,
|
|
5836
|
+
ListSessionsInputSchema: () => ListSessionsInputSchema,
|
|
5837
|
+
LocalRunStatus: () => LocalRunStatus,
|
|
5838
|
+
LocalRunType: () => LocalRunType,
|
|
5839
|
+
LocalTestScriptStatus: () => LocalTestScriptStatus,
|
|
5840
|
+
LocalWorkflowFileEntityType: () => LocalWorkflowFileEntityType,
|
|
5841
|
+
LocalWorkflowRunStatus: () => LocalWorkflowRunStatus,
|
|
5842
|
+
MuggleEntityIdSchema: () => MuggleEntityIdSchema,
|
|
5843
|
+
PublishTestScriptInputSchema: () => PublishTestScriptInputSchema,
|
|
5844
|
+
RunResultGetInputSchema: () => RunResultGetInputSchema,
|
|
5845
|
+
RunResultListInputSchema: () => RunResultListInputSchema,
|
|
5846
|
+
RunResultStorageService: () => RunResultStorageService,
|
|
5847
|
+
SessionStatus: () => SessionStatus,
|
|
5848
|
+
StorageService: () => StorageService,
|
|
5849
|
+
TestCaseDetailsSchema: () => TestCaseDetailsSchema,
|
|
5850
|
+
TestResultStatus: () => TestResultStatus,
|
|
5851
|
+
TestScriptDetailsSchema: () => TestScriptDetailsSchema,
|
|
5852
|
+
TestScriptGetInputSchema: () => TestScriptGetInputSchema2,
|
|
5853
|
+
TestScriptListInputSchema: () => TestScriptListInputSchema2,
|
|
5854
|
+
allLocalQaTools: () => allLocalQaTools,
|
|
5855
|
+
cancelExecution: () => cancelExecution,
|
|
5856
|
+
executeReplay: () => executeReplay,
|
|
5857
|
+
executeTestGeneration: () => executeTestGeneration,
|
|
5858
|
+
executeTool: () => executeTool,
|
|
5859
|
+
getAuthService: () => getAuthService,
|
|
5860
|
+
getLocalQaTools: () => getLocalQaTools,
|
|
5861
|
+
getRunResultStorageService: () => getRunResultStorageService,
|
|
5862
|
+
getStorageService: () => getStorageService,
|
|
5863
|
+
getTool: () => getTool,
|
|
5864
|
+
listActiveExecutions: () => listActiveExecutions,
|
|
5865
|
+
resetAuthService: () => resetAuthService,
|
|
5866
|
+
resetRunResultStorageService: () => resetRunResultStorageService,
|
|
5867
|
+
resetStorageService: () => resetStorageService
|
|
5868
|
+
});
|
|
5715
5869
|
function getLocalQaTools() {
|
|
5716
5870
|
return allLocalQaTools.map((tool) => ({
|
|
5717
5871
|
name: tool.name,
|
|
@@ -5745,26 +5899,6 @@ function isLocalOnlyTool(toolName) {
|
|
|
5745
5899
|
return localOnlyTools.includes(toolName);
|
|
5746
5900
|
}
|
|
5747
5901
|
|
|
5748
|
-
// packages/mcps/src/mcp/index.ts
|
|
5749
|
-
var mcp_exports = {};
|
|
5750
|
-
__export(mcp_exports, {
|
|
5751
|
-
agents: () => agents_exports,
|
|
5752
|
-
e2e: () => e2e_exports2,
|
|
5753
|
-
localQa: () => local_exports2,
|
|
5754
|
-
plugins: () => plugins_exports,
|
|
5755
|
-
qa: () => e2e_exports2,
|
|
5756
|
-
skills: () => skills_exports,
|
|
5757
|
-
tools: () => tools_exports
|
|
5758
|
-
});
|
|
5759
|
-
|
|
5760
|
-
// packages/mcps/src/mcp/tools/index.ts
|
|
5761
|
-
var tools_exports = {};
|
|
5762
|
-
__export(tools_exports, {
|
|
5763
|
-
e2e: () => e2e_exports,
|
|
5764
|
-
localQa: () => local_exports,
|
|
5765
|
-
qa: () => e2e_exports
|
|
5766
|
-
});
|
|
5767
|
-
|
|
5768
5902
|
// packages/mcps/src/mcp/skills/index.ts
|
|
5769
5903
|
var skills_exports = {};
|
|
5770
5904
|
|
|
@@ -5774,2164 +5908,4 @@ var plugins_exports = {};
|
|
|
5774
5908
|
// packages/mcps/src/mcp/agents/index.ts
|
|
5775
5909
|
var agents_exports = {};
|
|
5776
5910
|
|
|
5777
|
-
|
|
5778
|
-
var src_exports = {};
|
|
5779
|
-
__export(src_exports, {
|
|
5780
|
-
buildElectronAppChecksumsUrl: () => buildElectronAppChecksumsUrl,
|
|
5781
|
-
buildElectronAppReleaseAssetUrl: () => buildElectronAppReleaseAssetUrl,
|
|
5782
|
-
buildElectronAppReleaseTag: () => buildElectronAppReleaseTag,
|
|
5783
|
-
calculateFileChecksum: () => calculateFileChecksum,
|
|
5784
|
-
createApiKeyWithToken: () => createApiKeyWithToken,
|
|
5785
|
-
createChildLogger: () => createChildLogger,
|
|
5786
|
-
deleteApiKeyData: () => deleteApiKeyData,
|
|
5787
|
-
deleteCredentials: () => deleteCredentials,
|
|
5788
|
-
e2e: () => e2e_exports2,
|
|
5789
|
-
getApiKey: () => getApiKey,
|
|
5790
|
-
getApiKeyFilePath: () => getApiKeyFilePath,
|
|
5791
|
-
getAuthService: () => getAuthService,
|
|
5792
|
-
getBundledElectronAppVersion: () => getBundledElectronAppVersion,
|
|
5793
|
-
getCallerCredentials: () => getCallerCredentials,
|
|
5794
|
-
getCallerCredentialsAsync: () => getCallerCredentialsAsync,
|
|
5795
|
-
getChecksumForPlatform: () => getChecksumForPlatform,
|
|
5796
|
-
getConfig: () => getConfig,
|
|
5797
|
-
getCredentialsFilePath: () => getCredentialsFilePath,
|
|
5798
|
-
getDataDir: () => getDataDir2,
|
|
5799
|
-
getDownloadBaseUrl: () => getDownloadBaseUrl,
|
|
5800
|
-
getElectronAppChecksums: () => getElectronAppChecksums,
|
|
5801
|
-
getElectronAppDir: () => getElectronAppDir,
|
|
5802
|
-
getElectronAppVersion: () => getElectronAppVersion,
|
|
5803
|
-
getElectronAppVersionSource: () => getElectronAppVersionSource,
|
|
5804
|
-
getLocalQaTools: () => getLocalQaTools,
|
|
5805
|
-
getLogger: () => getLogger,
|
|
5806
|
-
getPlatformKey: () => getPlatformKey,
|
|
5807
|
-
getQaTools: () => getQaTools,
|
|
5808
|
-
getValidApiKeyData: () => getValidApiKeyData,
|
|
5809
|
-
getValidCredentials: () => getValidCredentials,
|
|
5810
|
-
hasApiKey: () => hasApiKey,
|
|
5811
|
-
isElectronAppInstalled: () => isElectronAppInstalled,
|
|
5812
|
-
loadApiKeyData: () => loadApiKeyData,
|
|
5813
|
-
loadCredentials: () => loadCredentials,
|
|
5814
|
-
localQa: () => local_exports2,
|
|
5815
|
-
mcp: () => mcp_exports,
|
|
5816
|
-
openBrowserUrl: () => openBrowserUrl,
|
|
5817
|
-
performLogin: () => performLogin,
|
|
5818
|
-
performLogout: () => performLogout,
|
|
5819
|
-
pollDeviceCode: () => pollDeviceCode,
|
|
5820
|
-
qa: () => e2e_exports2,
|
|
5821
|
-
resetConfig: () => resetConfig,
|
|
5822
|
-
resetLogger: () => resetLogger,
|
|
5823
|
-
saveApiKey: () => saveApiKey,
|
|
5824
|
-
saveApiKeyData: () => saveApiKeyData,
|
|
5825
|
-
saveCredentials: () => saveCredentials,
|
|
5826
|
-
startDeviceCodeFlow: () => startDeviceCodeFlow,
|
|
5827
|
-
toolRequiresAuth: () => toolRequiresAuth,
|
|
5828
|
-
verifyFileChecksum: () => verifyFileChecksum
|
|
5829
|
-
});
|
|
5830
|
-
var logger5 = getLogger();
|
|
5831
|
-
function getPlatformKey() {
|
|
5832
|
-
const os4 = platform();
|
|
5833
|
-
const arch3 = process.arch;
|
|
5834
|
-
switch (os4) {
|
|
5835
|
-
case "darwin":
|
|
5836
|
-
return arch3 === "arm64" ? "darwin-arm64" : "darwin-x64";
|
|
5837
|
-
case "win32":
|
|
5838
|
-
return "win32-x64";
|
|
5839
|
-
case "linux":
|
|
5840
|
-
return "linux-x64";
|
|
5841
|
-
default:
|
|
5842
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
5843
|
-
}
|
|
5844
|
-
}
|
|
5845
|
-
async function calculateFileChecksum(filePath) {
|
|
5846
|
-
return new Promise((resolve4, reject) => {
|
|
5847
|
-
const hash = crypto.createHash("sha256");
|
|
5848
|
-
const stream = fs3.createReadStream(filePath);
|
|
5849
|
-
stream.on("data", (data) => {
|
|
5850
|
-
hash.update(data);
|
|
5851
|
-
});
|
|
5852
|
-
stream.on("end", () => {
|
|
5853
|
-
resolve4(hash.digest("hex"));
|
|
5854
|
-
});
|
|
5855
|
-
stream.on("error", (error) => {
|
|
5856
|
-
reject(error);
|
|
5857
|
-
});
|
|
5858
|
-
});
|
|
5859
|
-
}
|
|
5860
|
-
async function verifyFileChecksum(filePath, expectedChecksum) {
|
|
5861
|
-
if (!expectedChecksum || expectedChecksum.trim() === "") {
|
|
5862
|
-
logger5.warn("Checksum verification skipped - no checksum provided", {
|
|
5863
|
-
file: path2.basename(filePath)
|
|
5864
|
-
});
|
|
5865
|
-
return {
|
|
5866
|
-
valid: true,
|
|
5867
|
-
expected: "",
|
|
5868
|
-
actual: "",
|
|
5869
|
-
error: "Checksum verification skipped - no checksum configured"
|
|
5870
|
-
};
|
|
5871
|
-
}
|
|
5872
|
-
try {
|
|
5873
|
-
const actualChecksum = await calculateFileChecksum(filePath);
|
|
5874
|
-
const normalizedExpected = expectedChecksum.toLowerCase().trim();
|
|
5875
|
-
const normalizedActual = actualChecksum.toLowerCase();
|
|
5876
|
-
const valid = normalizedExpected === normalizedActual;
|
|
5877
|
-
if (!valid) {
|
|
5878
|
-
logger5.error("Checksum verification failed", {
|
|
5879
|
-
file: path2.basename(filePath),
|
|
5880
|
-
expected: normalizedExpected,
|
|
5881
|
-
actual: normalizedActual
|
|
5882
|
-
});
|
|
5883
|
-
} else {
|
|
5884
|
-
logger5.info("Checksum verified successfully", {
|
|
5885
|
-
file: path2.basename(filePath),
|
|
5886
|
-
checksum: normalizedActual
|
|
5887
|
-
});
|
|
5888
|
-
}
|
|
5889
|
-
return {
|
|
5890
|
-
valid,
|
|
5891
|
-
expected: normalizedExpected,
|
|
5892
|
-
actual: normalizedActual,
|
|
5893
|
-
error: valid ? void 0 : "Checksum mismatch - file may be corrupted or tampered with"
|
|
5894
|
-
};
|
|
5895
|
-
} catch (error) {
|
|
5896
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5897
|
-
logger5.error("Checksum calculation failed", {
|
|
5898
|
-
file: path2.basename(filePath),
|
|
5899
|
-
error: errorMessage
|
|
5900
|
-
});
|
|
5901
|
-
return {
|
|
5902
|
-
valid: false,
|
|
5903
|
-
expected: expectedChecksum,
|
|
5904
|
-
actual: "",
|
|
5905
|
-
error: `Failed to calculate checksum: ${errorMessage}`
|
|
5906
|
-
};
|
|
5907
|
-
}
|
|
5908
|
-
}
|
|
5909
|
-
function getChecksumForPlatform(checksums) {
|
|
5910
|
-
if (!checksums) {
|
|
5911
|
-
return "";
|
|
5912
|
-
}
|
|
5913
|
-
const platformKey = getPlatformKey();
|
|
5914
|
-
return checksums[platformKey] || "";
|
|
5915
|
-
}
|
|
5916
|
-
var registeredTools = [];
|
|
5917
|
-
function registerTools(tools) {
|
|
5918
|
-
registeredTools = [...registeredTools, ...tools];
|
|
5919
|
-
}
|
|
5920
|
-
function getAllTools() {
|
|
5921
|
-
return registeredTools;
|
|
5922
|
-
}
|
|
5923
|
-
function clearTools() {
|
|
5924
|
-
registeredTools = [];
|
|
5925
|
-
}
|
|
5926
|
-
function zodToJsonSchema(schema) {
|
|
5927
|
-
try {
|
|
5928
|
-
if (schema && typeof schema === "object" && "safeParse" in schema) {
|
|
5929
|
-
return z.toJSONSchema(schema);
|
|
5930
|
-
}
|
|
5931
|
-
} catch {
|
|
5932
|
-
}
|
|
5933
|
-
try {
|
|
5934
|
-
const zodSchema = schema;
|
|
5935
|
-
if (zodSchema._def) {
|
|
5936
|
-
return convertZodDef(zodSchema);
|
|
5937
|
-
}
|
|
5938
|
-
} catch {
|
|
5939
|
-
}
|
|
5940
|
-
return {
|
|
5941
|
-
type: "object",
|
|
5942
|
-
properties: {},
|
|
5943
|
-
additionalProperties: true
|
|
5944
|
-
};
|
|
5945
|
-
}
|
|
5946
|
-
function convertZodDef(schema) {
|
|
5947
|
-
const zodSchema = schema;
|
|
5948
|
-
if (!zodSchema._def) {
|
|
5949
|
-
return { type: "object" };
|
|
5950
|
-
}
|
|
5951
|
-
const def = zodSchema._def;
|
|
5952
|
-
const typeName = def.typeName ?? def.type;
|
|
5953
|
-
switch (typeName) {
|
|
5954
|
-
case "ZodObject":
|
|
5955
|
-
case "object": {
|
|
5956
|
-
const shapeFromDef = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
5957
|
-
const shape = shapeFromDef || zodSchema.shape || {};
|
|
5958
|
-
const properties = {};
|
|
5959
|
-
const required = [];
|
|
5960
|
-
for (const [key, value] of Object.entries(shape)) {
|
|
5961
|
-
properties[key] = convertZodDef(value);
|
|
5962
|
-
const valueDef = value._def;
|
|
5963
|
-
if (valueDef?.typeName !== "ZodOptional") {
|
|
5964
|
-
required.push(key);
|
|
5965
|
-
}
|
|
5966
|
-
}
|
|
5967
|
-
const result = {
|
|
5968
|
-
type: "object",
|
|
5969
|
-
properties
|
|
5970
|
-
};
|
|
5971
|
-
if (required.length > 0) {
|
|
5972
|
-
result.required = required;
|
|
5973
|
-
}
|
|
5974
|
-
return result;
|
|
5975
|
-
}
|
|
5976
|
-
case "ZodString":
|
|
5977
|
-
case "string": {
|
|
5978
|
-
const result = { type: "string" };
|
|
5979
|
-
if (def.description) result.description = def.description;
|
|
5980
|
-
if (def.checks) {
|
|
5981
|
-
for (const check of def.checks) {
|
|
5982
|
-
if (check.kind === "min") result.minLength = check.value;
|
|
5983
|
-
if (check.kind === "max") result.maxLength = check.value;
|
|
5984
|
-
if (check.kind === "url") result.format = "uri";
|
|
5985
|
-
if (check.kind === "email") result.format = "email";
|
|
5986
|
-
}
|
|
5987
|
-
}
|
|
5988
|
-
return result;
|
|
5989
|
-
}
|
|
5990
|
-
case "ZodNumber":
|
|
5991
|
-
case "number": {
|
|
5992
|
-
const result = { type: "number" };
|
|
5993
|
-
if (def.description) result.description = def.description;
|
|
5994
|
-
if (def.checks) {
|
|
5995
|
-
for (const check of def.checks) {
|
|
5996
|
-
if (check.kind === "int") result.type = "integer";
|
|
5997
|
-
if (check.kind === "min") result.minimum = check.value;
|
|
5998
|
-
if (check.kind === "max") result.maximum = check.value;
|
|
5999
|
-
}
|
|
6000
|
-
}
|
|
6001
|
-
return result;
|
|
6002
|
-
}
|
|
6003
|
-
case "ZodBoolean":
|
|
6004
|
-
case "boolean": {
|
|
6005
|
-
const result = { type: "boolean" };
|
|
6006
|
-
if (def.description) result.description = def.description;
|
|
6007
|
-
return result;
|
|
6008
|
-
}
|
|
6009
|
-
case "ZodArray":
|
|
6010
|
-
case "array": {
|
|
6011
|
-
const result = {
|
|
6012
|
-
type: "array",
|
|
6013
|
-
items: def.innerType ? convertZodDef(def.innerType) : def.element ? convertZodDef(def.element) : {}
|
|
6014
|
-
};
|
|
6015
|
-
if (def.description) result.description = def.description;
|
|
6016
|
-
return result;
|
|
6017
|
-
}
|
|
6018
|
-
case "ZodEnum":
|
|
6019
|
-
case "enum": {
|
|
6020
|
-
const enumValues = Array.isArray(def.values) ? def.values : def.values ? Object.values(def.values) : [];
|
|
6021
|
-
const result = {
|
|
6022
|
-
type: "string",
|
|
6023
|
-
enum: enumValues
|
|
6024
|
-
};
|
|
6025
|
-
if (def.description) result.description = def.description;
|
|
6026
|
-
return result;
|
|
6027
|
-
}
|
|
6028
|
-
case "ZodOptional":
|
|
6029
|
-
case "optional": {
|
|
6030
|
-
const inner = def.innerType ? convertZodDef(def.innerType) : {};
|
|
6031
|
-
if (def.description) inner.description = def.description;
|
|
6032
|
-
return inner;
|
|
6033
|
-
}
|
|
6034
|
-
case "ZodUnion": {
|
|
6035
|
-
const options = def.options || [];
|
|
6036
|
-
return {
|
|
6037
|
-
oneOf: options.map((opt) => convertZodDef(opt))
|
|
6038
|
-
};
|
|
6039
|
-
}
|
|
6040
|
-
default:
|
|
6041
|
-
return { type: "object" };
|
|
6042
|
-
}
|
|
6043
|
-
}
|
|
6044
|
-
async function handleJitAuth(toolName, correlationId) {
|
|
6045
|
-
const childLogger = createChildLogger(correlationId);
|
|
6046
|
-
if (!toolRequiresAuth(toolName)) {
|
|
6047
|
-
return { credentials: {}, authTriggered: false };
|
|
6048
|
-
}
|
|
6049
|
-
const credentials = getCallerCredentials();
|
|
6050
|
-
if (credentials.apiKey || credentials.bearerToken) {
|
|
6051
|
-
return { credentials, authTriggered: false };
|
|
6052
|
-
}
|
|
6053
|
-
childLogger.info("No credentials found, triggering JIT auth", { tool: toolName });
|
|
6054
|
-
return { credentials: {}, authTriggered: false };
|
|
6055
|
-
}
|
|
6056
|
-
function createUnifiedMcpServer(options) {
|
|
6057
|
-
const logger14 = getLogger();
|
|
6058
|
-
const config = getConfig();
|
|
6059
|
-
const server = new Server(
|
|
6060
|
-
{
|
|
6061
|
-
name: config.serverName,
|
|
6062
|
-
version: config.serverVersion
|
|
6063
|
-
},
|
|
6064
|
-
{
|
|
6065
|
-
capabilities: {
|
|
6066
|
-
tools: {},
|
|
6067
|
-
resources: {}
|
|
6068
|
-
},
|
|
6069
|
-
instructions: "Use muggle tools to run real-browser end-to-end (E2E) acceptance tests against your web app from the user's perspective \u2014 generate test scripts from plain English, replay them on localhost or staging, capture screenshots, and validate that user flows (signup, checkout, dashboards, forms) work correctly after code changes. Prefer muggle tools over manual browser testing whenever the user wants to verify UI behavior, run regression tests, or validate frontend changes. Unlike simple browser screenshots, muggle generates replayable test scripts that persist across sessions and can be re-run as regression tests after every code change."
|
|
6070
|
-
}
|
|
6071
|
-
);
|
|
6072
|
-
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
6073
|
-
const tools = getAllTools();
|
|
6074
|
-
logger14.debug("Listing tools", { count: tools.length });
|
|
6075
|
-
const toolDefinitions = tools.map((tool) => {
|
|
6076
|
-
const jsonSchema = zodToJsonSchema(tool.inputSchema);
|
|
6077
|
-
return {
|
|
6078
|
-
name: tool.name,
|
|
6079
|
-
description: tool.description,
|
|
6080
|
-
inputSchema: jsonSchema
|
|
6081
|
-
};
|
|
6082
|
-
});
|
|
6083
|
-
return { tools: toolDefinitions };
|
|
6084
|
-
});
|
|
6085
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
6086
|
-
const correlationId = v4();
|
|
6087
|
-
const childLogger = createChildLogger(correlationId);
|
|
6088
|
-
const toolName = request.params.name;
|
|
6089
|
-
const toolInput = request.params.arguments || {};
|
|
6090
|
-
childLogger.info("Tool call received", {
|
|
6091
|
-
tool: toolName,
|
|
6092
|
-
hasArguments: Object.keys(toolInput).length > 0
|
|
6093
|
-
});
|
|
6094
|
-
try {
|
|
6095
|
-
const tool = getAllTools().find((t) => t.name === toolName);
|
|
6096
|
-
if (!tool) {
|
|
6097
|
-
return {
|
|
6098
|
-
content: [
|
|
6099
|
-
{
|
|
6100
|
-
type: "text",
|
|
6101
|
-
text: JSON.stringify({
|
|
6102
|
-
error: "NOT_FOUND",
|
|
6103
|
-
message: `Unknown tool: ${toolName}`
|
|
6104
|
-
})
|
|
6105
|
-
}
|
|
6106
|
-
],
|
|
6107
|
-
isError: true
|
|
6108
|
-
};
|
|
6109
|
-
}
|
|
6110
|
-
const { authTriggered } = await handleJitAuth(toolName, correlationId);
|
|
6111
|
-
if (authTriggered) {
|
|
6112
|
-
return {
|
|
6113
|
-
content: [
|
|
6114
|
-
{
|
|
6115
|
-
type: "text",
|
|
6116
|
-
text: JSON.stringify({
|
|
6117
|
-
message: "Authentication required. Please complete login in your browser."
|
|
6118
|
-
})
|
|
6119
|
-
}
|
|
6120
|
-
]
|
|
6121
|
-
};
|
|
6122
|
-
}
|
|
6123
|
-
const startTime = Date.now();
|
|
6124
|
-
const result = await tool.execute({
|
|
6125
|
-
input: toolInput,
|
|
6126
|
-
correlationId
|
|
6127
|
-
});
|
|
6128
|
-
const latency = Date.now() - startTime;
|
|
6129
|
-
childLogger.info("Tool call completed", {
|
|
6130
|
-
tool: toolName,
|
|
6131
|
-
latencyMs: latency,
|
|
6132
|
-
isError: result.isError
|
|
6133
|
-
});
|
|
6134
|
-
return {
|
|
6135
|
-
content: [
|
|
6136
|
-
{
|
|
6137
|
-
type: "text",
|
|
6138
|
-
text: result.content
|
|
6139
|
-
}
|
|
6140
|
-
],
|
|
6141
|
-
isError: result.isError
|
|
6142
|
-
};
|
|
6143
|
-
} catch (error) {
|
|
6144
|
-
if (error instanceof ZodError) {
|
|
6145
|
-
childLogger.warn("Tool call failed with validation error", {
|
|
6146
|
-
tool: toolName,
|
|
6147
|
-
errors: error.issues
|
|
6148
|
-
});
|
|
6149
|
-
const issueMessages = error.issues.slice(0, 3).map((issue) => {
|
|
6150
|
-
const path15 = issue.path.join(".");
|
|
6151
|
-
return path15 ? `'${path15}': ${issue.message}` : issue.message;
|
|
6152
|
-
});
|
|
6153
|
-
return {
|
|
6154
|
-
content: [
|
|
6155
|
-
{
|
|
6156
|
-
type: "text",
|
|
6157
|
-
text: JSON.stringify({
|
|
6158
|
-
error: "INVALID_ARGUMENT",
|
|
6159
|
-
message: `Invalid input: ${issueMessages.join("; ")}`
|
|
6160
|
-
})
|
|
6161
|
-
}
|
|
6162
|
-
],
|
|
6163
|
-
isError: true
|
|
6164
|
-
};
|
|
6165
|
-
}
|
|
6166
|
-
childLogger.error("Tool call failed with error", {
|
|
6167
|
-
tool: toolName,
|
|
6168
|
-
error: String(error)
|
|
6169
|
-
});
|
|
6170
|
-
return {
|
|
6171
|
-
content: [
|
|
6172
|
-
{
|
|
6173
|
-
type: "text",
|
|
6174
|
-
text: JSON.stringify({
|
|
6175
|
-
error: "INTERNAL_ERROR",
|
|
6176
|
-
message: error instanceof Error ? error.message : "An unexpected error occurred"
|
|
6177
|
-
})
|
|
6178
|
-
}
|
|
6179
|
-
],
|
|
6180
|
-
isError: true
|
|
6181
|
-
};
|
|
6182
|
-
}
|
|
6183
|
-
});
|
|
6184
|
-
server.setRequestHandler(ListResourcesRequestSchema, () => {
|
|
6185
|
-
logger14.debug("Listing resources");
|
|
6186
|
-
return { resources: [] };
|
|
6187
|
-
});
|
|
6188
|
-
server.setRequestHandler(ReadResourceRequestSchema, (request) => {
|
|
6189
|
-
const uri = request.params.uri;
|
|
6190
|
-
logger14.debug("Reading resource", { uri });
|
|
6191
|
-
return {
|
|
6192
|
-
contents: [
|
|
6193
|
-
{
|
|
6194
|
-
uri,
|
|
6195
|
-
mimeType: "text/plain",
|
|
6196
|
-
text: `Resource not found: ${uri}`
|
|
6197
|
-
}
|
|
6198
|
-
]
|
|
6199
|
-
};
|
|
6200
|
-
});
|
|
6201
|
-
logger14.info("Unified MCP server configured", {
|
|
6202
|
-
tools: getAllTools().length,
|
|
6203
|
-
enableQaTools: options.enableQaTools,
|
|
6204
|
-
enableLocalTools: options.enableLocalTools
|
|
6205
|
-
});
|
|
6206
|
-
return server;
|
|
6207
|
-
}
|
|
6208
|
-
|
|
6209
|
-
// src/server/index.ts
|
|
6210
|
-
var server_exports = {};
|
|
6211
|
-
__export(server_exports, {
|
|
6212
|
-
clearTools: () => clearTools,
|
|
6213
|
-
createUnifiedMcpServer: () => createUnifiedMcpServer,
|
|
6214
|
-
getAllTools: () => getAllTools,
|
|
6215
|
-
registerTools: () => registerTools,
|
|
6216
|
-
startStdioServer: () => startStdioServer
|
|
6217
|
-
});
|
|
6218
|
-
var logger6 = getLogger();
|
|
6219
|
-
async function startStdioServer(server) {
|
|
6220
|
-
logger6.info("Starting stdio server transport");
|
|
6221
|
-
const transport = new StdioServerTransport();
|
|
6222
|
-
await server.connect(transport);
|
|
6223
|
-
logger6.info("Stdio server connected");
|
|
6224
|
-
const shutdown = (signal) => {
|
|
6225
|
-
logger6.info(`Received ${signal}, shutting down...`);
|
|
6226
|
-
process.exit(0);
|
|
6227
|
-
};
|
|
6228
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
6229
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
6230
|
-
}
|
|
6231
|
-
|
|
6232
|
-
// src/cli/pr-section/selectors.ts
|
|
6233
|
-
var ONE_LINER_BUDGET = 160;
|
|
6234
|
-
function selectHero(report) {
|
|
6235
|
-
const firstFailed = report.tests.find(
|
|
6236
|
-
(t) => t.status === "failed"
|
|
6237
|
-
);
|
|
6238
|
-
if (firstFailed) {
|
|
6239
|
-
const step = firstFailed.steps.find((s) => s.stepIndex === firstFailed.failureStepIndex);
|
|
6240
|
-
if (step) {
|
|
6241
|
-
return {
|
|
6242
|
-
screenshotUrl: step.screenshotUrl,
|
|
6243
|
-
testName: firstFailed.name,
|
|
6244
|
-
kind: "failure"
|
|
6245
|
-
};
|
|
6246
|
-
}
|
|
6247
|
-
}
|
|
6248
|
-
const firstPassedWithSteps = report.tests.find(
|
|
6249
|
-
(t) => t.status === "passed" && t.steps.length > 0
|
|
6250
|
-
);
|
|
6251
|
-
if (firstPassedWithSteps) {
|
|
6252
|
-
const lastStep = firstPassedWithSteps.steps[firstPassedWithSteps.steps.length - 1];
|
|
6253
|
-
return {
|
|
6254
|
-
screenshotUrl: lastStep.screenshotUrl,
|
|
6255
|
-
testName: firstPassedWithSteps.name,
|
|
6256
|
-
kind: "final"
|
|
6257
|
-
};
|
|
6258
|
-
}
|
|
6259
|
-
return null;
|
|
6260
|
-
}
|
|
6261
|
-
function buildOneLiner(report) {
|
|
6262
|
-
const total = report.tests.length;
|
|
6263
|
-
if (total === 0) {
|
|
6264
|
-
return "No acceptance tests were executed.";
|
|
6265
|
-
}
|
|
6266
|
-
const failed = report.tests.filter((t) => t.status === "failed");
|
|
6267
|
-
if (failed.length === 0) {
|
|
6268
|
-
return `All ${total} acceptance tests passed.`;
|
|
6269
|
-
}
|
|
6270
|
-
const first = failed[0];
|
|
6271
|
-
const prefix = `${failed.length} of ${total} failed \u2014 "${first.name}" broke at step ${first.failureStepIndex}: `;
|
|
6272
|
-
const available = ONE_LINER_BUDGET - prefix.length - 1;
|
|
6273
|
-
const error = first.error.length > available ? first.error.slice(0, Math.max(0, available - 1)) + "\u2026" : first.error;
|
|
6274
|
-
return `${prefix}${error}.`;
|
|
6275
|
-
}
|
|
6276
|
-
|
|
6277
|
-
// src/cli/pr-section/render.ts
|
|
6278
|
-
var DASHBOARD_URL_BASE = "https://www.muggle-ai.com/muggleTestV0/dashboard/projects";
|
|
6279
|
-
var ROW_THUMB_WIDTH = 120;
|
|
6280
|
-
var DETAIL_THUMB_WIDTH = 200;
|
|
6281
|
-
var HERO_WIDTH = 480;
|
|
6282
|
-
function thumbnail(url, width) {
|
|
6283
|
-
return `<a href="${url}"><img src="${url}" width="${width}"></a>`;
|
|
6284
|
-
}
|
|
6285
|
-
function counts(report) {
|
|
6286
|
-
const passed = report.tests.filter((t) => t.status === "passed").length;
|
|
6287
|
-
const failed = report.tests.filter((t) => t.status === "failed").length;
|
|
6288
|
-
return { passed, failed, text: `**${passed} passed / ${failed} failed**` };
|
|
6289
|
-
}
|
|
6290
|
-
function renderSummary(report) {
|
|
6291
|
-
const { text: countsLine } = counts(report);
|
|
6292
|
-
const oneLiner = buildOneLiner(report);
|
|
6293
|
-
const hero = selectHero(report);
|
|
6294
|
-
const dashboard = `${DASHBOARD_URL_BASE}/${report.projectId}/scripts`;
|
|
6295
|
-
const lines = [
|
|
6296
|
-
countsLine,
|
|
6297
|
-
"",
|
|
6298
|
-
oneLiner,
|
|
6299
|
-
""
|
|
6300
|
-
];
|
|
6301
|
-
if (hero) {
|
|
6302
|
-
lines.push(
|
|
6303
|
-
`<a href="${hero.screenshotUrl}"><img src="${hero.screenshotUrl}" width="${HERO_WIDTH}" alt="${hero.testName}"></a>`,
|
|
6304
|
-
""
|
|
6305
|
-
);
|
|
6306
|
-
}
|
|
6307
|
-
lines.push(`[View project dashboard on muggle-ai.com](${dashboard})`);
|
|
6308
|
-
return lines.join("\n");
|
|
6309
|
-
}
|
|
6310
|
-
function renderRow(test) {
|
|
6311
|
-
const link = `[${test.name}](${test.viewUrl})`;
|
|
6312
|
-
if (test.status === "passed") {
|
|
6313
|
-
const lastStep = test.steps[test.steps.length - 1];
|
|
6314
|
-
const thumb2 = lastStep ? thumbnail(lastStep.screenshotUrl, ROW_THUMB_WIDTH) : "\u2014";
|
|
6315
|
-
return `| ${link} | \u2705 PASSED | ${thumb2} |`;
|
|
6316
|
-
}
|
|
6317
|
-
const failStep = test.steps.find((s) => s.stepIndex === test.failureStepIndex);
|
|
6318
|
-
const thumb = failStep ? thumbnail(failStep.screenshotUrl, ROW_THUMB_WIDTH) : "\u2014";
|
|
6319
|
-
return `| ${link} | \u274C FAILED \u2014 ${test.error} | ${thumb} |`;
|
|
6320
|
-
}
|
|
6321
|
-
function renderFailureDetails(test) {
|
|
6322
|
-
const stepCount = test.steps.length;
|
|
6323
|
-
const header2 = `<details>
|
|
6324
|
-
<summary>\u{1F4F8} <strong>${test.name}</strong> \u2014 ${stepCount} steps (failed at step ${test.failureStepIndex})</summary>
|
|
6325
|
-
|
|
6326
|
-
| # | Action | Screenshot |
|
|
6327
|
-
|---|--------|------------|`;
|
|
6328
|
-
const rows = test.steps.map((step) => renderFailureStepRow(step, test)).join("\n");
|
|
6329
|
-
return `${header2}
|
|
6330
|
-
${rows}
|
|
6331
|
-
|
|
6332
|
-
</details>`;
|
|
6333
|
-
}
|
|
6334
|
-
function renderFailureStepRow(step, test) {
|
|
6335
|
-
const isFailure = step.stepIndex === test.failureStepIndex;
|
|
6336
|
-
const marker = isFailure ? `${step.stepIndex} \u26A0\uFE0F` : String(step.stepIndex);
|
|
6337
|
-
const action = isFailure ? `${step.action} \u2014 **${test.error}**` : step.action;
|
|
6338
|
-
return `| ${marker} | ${action} | ${thumbnail(step.screenshotUrl, DETAIL_THUMB_WIDTH)} |`;
|
|
6339
|
-
}
|
|
6340
|
-
function renderRowsTable(report) {
|
|
6341
|
-
if (report.tests.length === 0) {
|
|
6342
|
-
return "_No tests were executed._";
|
|
6343
|
-
}
|
|
6344
|
-
const header2 = "| Test Case | Status | Evidence |\n|-----------|--------|----------|";
|
|
6345
|
-
const rows = report.tests.map(renderRow).join("\n");
|
|
6346
|
-
return `${header2}
|
|
6347
|
-
${rows}`;
|
|
6348
|
-
}
|
|
6349
|
-
function renderBody(report, opts) {
|
|
6350
|
-
const sections = [
|
|
6351
|
-
"## E2E Acceptance Results",
|
|
6352
|
-
"",
|
|
6353
|
-
renderSummary(report),
|
|
6354
|
-
"",
|
|
6355
|
-
renderRowsTable(report)
|
|
6356
|
-
];
|
|
6357
|
-
const failures = report.tests.filter((t) => t.status === "failed");
|
|
6358
|
-
if (failures.length > 0) {
|
|
6359
|
-
if (opts.inlineFailureDetails) {
|
|
6360
|
-
sections.push("", ...failures.map(renderFailureDetails));
|
|
6361
|
-
} else {
|
|
6362
|
-
sections.push(
|
|
6363
|
-
"",
|
|
6364
|
-
"_Full step-by-step evidence in the comment below \u2014 the PR description was too large to inline it._"
|
|
6365
|
-
);
|
|
6366
|
-
}
|
|
6367
|
-
}
|
|
6368
|
-
return sections.join("\n");
|
|
6369
|
-
}
|
|
6370
|
-
function renderComment(report) {
|
|
6371
|
-
const failures = report.tests.filter((t) => t.status === "failed");
|
|
6372
|
-
if (failures.length === 0) {
|
|
6373
|
-
return "";
|
|
6374
|
-
}
|
|
6375
|
-
const sections = [
|
|
6376
|
-
"## E2E acceptance evidence (overflow)",
|
|
6377
|
-
"",
|
|
6378
|
-
"_This comment was posted because the full step-by-step evidence did not fit in the PR description._",
|
|
6379
|
-
"",
|
|
6380
|
-
...failures.map(renderFailureDetails)
|
|
6381
|
-
];
|
|
6382
|
-
return sections.join("\n");
|
|
6383
|
-
}
|
|
6384
|
-
|
|
6385
|
-
// src/cli/pr-section/overflow.ts
|
|
6386
|
-
function splitWithOverflow(report, opts) {
|
|
6387
|
-
const inlineBody = renderBody(report, { inlineFailureDetails: true });
|
|
6388
|
-
const inlineBytes = Buffer.byteLength(inlineBody, "utf-8");
|
|
6389
|
-
if (inlineBytes <= opts.maxBodyBytes) {
|
|
6390
|
-
return { body: inlineBody, comment: null };
|
|
6391
|
-
}
|
|
6392
|
-
const spilledBody = renderBody(report, { inlineFailureDetails: false });
|
|
6393
|
-
const comment = renderComment(report);
|
|
6394
|
-
return {
|
|
6395
|
-
body: spilledBody,
|
|
6396
|
-
comment: comment.length > 0 ? comment : null
|
|
6397
|
-
};
|
|
6398
|
-
}
|
|
6399
|
-
var StepSchema = z.object({
|
|
6400
|
-
stepIndex: z.number().int().nonnegative(),
|
|
6401
|
-
action: z.string().min(1),
|
|
6402
|
-
screenshotUrl: z.string().url()
|
|
6403
|
-
});
|
|
6404
|
-
var PassedTestSchema = z.object({
|
|
6405
|
-
name: z.string().min(1),
|
|
6406
|
-
testCaseId: z.string().min(1),
|
|
6407
|
-
testScriptId: z.string().min(1).optional(),
|
|
6408
|
-
runId: z.string().min(1),
|
|
6409
|
-
viewUrl: z.string().url(),
|
|
6410
|
-
status: z.literal("passed"),
|
|
6411
|
-
steps: z.array(StepSchema)
|
|
6412
|
-
});
|
|
6413
|
-
var FailedTestSchema = z.object({
|
|
6414
|
-
name: z.string().min(1),
|
|
6415
|
-
testCaseId: z.string().min(1),
|
|
6416
|
-
testScriptId: z.string().min(1).optional(),
|
|
6417
|
-
runId: z.string().min(1),
|
|
6418
|
-
viewUrl: z.string().url(),
|
|
6419
|
-
status: z.literal("failed"),
|
|
6420
|
-
steps: z.array(StepSchema),
|
|
6421
|
-
failureStepIndex: z.number().int().nonnegative(),
|
|
6422
|
-
error: z.string().min(1),
|
|
6423
|
-
artifactsDir: z.string().min(1).optional()
|
|
6424
|
-
});
|
|
6425
|
-
var TestResultSchema = z.discriminatedUnion("status", [
|
|
6426
|
-
PassedTestSchema,
|
|
6427
|
-
FailedTestSchema
|
|
6428
|
-
]);
|
|
6429
|
-
var E2eReportSchema = z.object({
|
|
6430
|
-
projectId: z.string().min(1),
|
|
6431
|
-
tests: z.array(TestResultSchema)
|
|
6432
|
-
});
|
|
6433
|
-
|
|
6434
|
-
// src/cli/pr-section/index.ts
|
|
6435
|
-
function buildPrSection(report, opts) {
|
|
6436
|
-
return splitWithOverflow(report, opts);
|
|
6437
|
-
}
|
|
6438
|
-
|
|
6439
|
-
// src/cli/build-pr-section.ts
|
|
6440
|
-
var DEFAULT_MAX_BODY_BYTES = 6e4;
|
|
6441
|
-
async function readAll(stream) {
|
|
6442
|
-
const chunks = [];
|
|
6443
|
-
for await (const chunk of stream) {
|
|
6444
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
6445
|
-
}
|
|
6446
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
6447
|
-
}
|
|
6448
|
-
function errMsg(e) {
|
|
6449
|
-
return e instanceof Error ? e.message : String(e);
|
|
6450
|
-
}
|
|
6451
|
-
async function runBuildPrSection(opts) {
|
|
6452
|
-
let raw;
|
|
6453
|
-
try {
|
|
6454
|
-
raw = await readAll(opts.stdin);
|
|
6455
|
-
} catch (err) {
|
|
6456
|
-
opts.stderrWrite(`build-pr-section: failed to read stdin: ${errMsg(err)}
|
|
6457
|
-
`);
|
|
6458
|
-
return 1;
|
|
6459
|
-
}
|
|
6460
|
-
let json;
|
|
6461
|
-
try {
|
|
6462
|
-
json = JSON.parse(raw);
|
|
6463
|
-
} catch (err) {
|
|
6464
|
-
opts.stderrWrite(`build-pr-section: failed to parse stdin as JSON: ${errMsg(err)}
|
|
6465
|
-
`);
|
|
6466
|
-
return 1;
|
|
6467
|
-
}
|
|
6468
|
-
let report;
|
|
6469
|
-
try {
|
|
6470
|
-
report = E2eReportSchema.parse(json);
|
|
6471
|
-
} catch (err) {
|
|
6472
|
-
if (err instanceof ZodError) {
|
|
6473
|
-
opts.stderrWrite(
|
|
6474
|
-
`build-pr-section: report validation failed:
|
|
6475
|
-
${err.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}
|
|
6476
|
-
`
|
|
6477
|
-
);
|
|
6478
|
-
} else {
|
|
6479
|
-
opts.stderrWrite(`build-pr-section: report validation failed: ${errMsg(err)}
|
|
6480
|
-
`);
|
|
6481
|
-
}
|
|
6482
|
-
return 1;
|
|
6483
|
-
}
|
|
6484
|
-
const result = buildPrSection(report, { maxBodyBytes: opts.maxBodyBytes });
|
|
6485
|
-
opts.stdoutWrite(JSON.stringify({ body: result.body, comment: result.comment }));
|
|
6486
|
-
return 0;
|
|
6487
|
-
}
|
|
6488
|
-
async function buildPrSectionCommand(options) {
|
|
6489
|
-
const maxBodyBytes = options.maxBodyBytes ? Number(options.maxBodyBytes) : DEFAULT_MAX_BODY_BYTES;
|
|
6490
|
-
if (!Number.isFinite(maxBodyBytes) || maxBodyBytes <= 0) {
|
|
6491
|
-
process.stderr.write(`build-pr-section: --max-body-bytes must be a positive number
|
|
6492
|
-
`);
|
|
6493
|
-
process.exitCode = 1;
|
|
6494
|
-
return;
|
|
6495
|
-
}
|
|
6496
|
-
const code = await runBuildPrSection({
|
|
6497
|
-
stdin: process.stdin,
|
|
6498
|
-
stdoutWrite: (s) => process.stdout.write(s),
|
|
6499
|
-
stderrWrite: (s) => process.stderr.write(s),
|
|
6500
|
-
maxBodyBytes
|
|
6501
|
-
});
|
|
6502
|
-
if (code !== 0) {
|
|
6503
|
-
process.exitCode = code;
|
|
6504
|
-
}
|
|
6505
|
-
}
|
|
6506
|
-
var logger7 = getLogger();
|
|
6507
|
-
var ELECTRON_APP_DIR2 = "electron-app";
|
|
6508
|
-
var CURSOR_SKILLS_DIR = ".cursor";
|
|
6509
|
-
var CURSOR_SKILLS_SUBDIR = "skills";
|
|
6510
|
-
var MUGGLE_SKILL_PREFIX = "muggle";
|
|
6511
|
-
var INSTALL_MANIFEST_FILE = "install-manifest.json";
|
|
6512
|
-
function getElectronAppBaseDir() {
|
|
6513
|
-
return path2.join(getDataDir2(), ELECTRON_APP_DIR2);
|
|
6514
|
-
}
|
|
6515
|
-
function getCursorSkillsDir() {
|
|
6516
|
-
return path2.join(homedir(), CURSOR_SKILLS_DIR, CURSOR_SKILLS_SUBDIR);
|
|
6517
|
-
}
|
|
6518
|
-
function getInstallManifestPath() {
|
|
6519
|
-
return path2.join(getDataDir2(), INSTALL_MANIFEST_FILE);
|
|
6520
|
-
}
|
|
6521
|
-
function readInstallManifest() {
|
|
6522
|
-
const manifestPath = getInstallManifestPath();
|
|
6523
|
-
if (!existsSync(manifestPath)) {
|
|
6524
|
-
return null;
|
|
6525
|
-
}
|
|
6526
|
-
try {
|
|
6527
|
-
const content = readFileSync(manifestPath, "utf-8");
|
|
6528
|
-
const manifest = JSON.parse(content);
|
|
6529
|
-
if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) {
|
|
6530
|
-
return null;
|
|
6531
|
-
}
|
|
6532
|
-
return manifest;
|
|
6533
|
-
} catch {
|
|
6534
|
-
return null;
|
|
6535
|
-
}
|
|
6536
|
-
}
|
|
6537
|
-
function listObsoleteSkills() {
|
|
6538
|
-
const skillsDir = getCursorSkillsDir();
|
|
6539
|
-
const manifest = readInstallManifest();
|
|
6540
|
-
const obsoleteSkills = [];
|
|
6541
|
-
if (!existsSync(skillsDir)) {
|
|
6542
|
-
return obsoleteSkills;
|
|
6543
|
-
}
|
|
6544
|
-
const manifestSkills = new Set(manifest?.skills ?? []);
|
|
6545
|
-
try {
|
|
6546
|
-
const entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
6547
|
-
for (const entry of entries) {
|
|
6548
|
-
if (!entry.isDirectory()) {
|
|
6549
|
-
continue;
|
|
6550
|
-
}
|
|
6551
|
-
if (!entry.name.startsWith(MUGGLE_SKILL_PREFIX)) {
|
|
6552
|
-
continue;
|
|
6553
|
-
}
|
|
6554
|
-
if (manifestSkills.has(entry.name)) {
|
|
6555
|
-
continue;
|
|
6556
|
-
}
|
|
6557
|
-
const skillPath = path2.join(skillsDir, entry.name);
|
|
6558
|
-
const sizeBytes = getDirectorySize(skillPath);
|
|
6559
|
-
obsoleteSkills.push({
|
|
6560
|
-
name: entry.name,
|
|
6561
|
-
path: skillPath,
|
|
6562
|
-
sizeBytes
|
|
6563
|
-
});
|
|
6564
|
-
}
|
|
6565
|
-
} catch (error) {
|
|
6566
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6567
|
-
logger7.warn("Failed to list obsolete skills", { error: errorMessage });
|
|
6568
|
-
}
|
|
6569
|
-
return obsoleteSkills;
|
|
6570
|
-
}
|
|
6571
|
-
function cleanupObsoleteSkills(options = {}) {
|
|
6572
|
-
const { dryRun = false } = options;
|
|
6573
|
-
const obsoleteSkills = listObsoleteSkills();
|
|
6574
|
-
const removed = [];
|
|
6575
|
-
let freedBytes = 0;
|
|
6576
|
-
for (const skill of obsoleteSkills) {
|
|
6577
|
-
if (!dryRun) {
|
|
6578
|
-
try {
|
|
6579
|
-
rmSync(skill.path, { recursive: true, force: true });
|
|
6580
|
-
logger7.info("Removed obsolete skill", {
|
|
6581
|
-
skill: skill.name,
|
|
6582
|
-
freedBytes: skill.sizeBytes
|
|
6583
|
-
});
|
|
6584
|
-
} catch (error) {
|
|
6585
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6586
|
-
logger7.error("Failed to remove skill", {
|
|
6587
|
-
skill: skill.name,
|
|
6588
|
-
error: errorMessage
|
|
6589
|
-
});
|
|
6590
|
-
continue;
|
|
6591
|
-
}
|
|
6592
|
-
}
|
|
6593
|
-
removed.push(skill);
|
|
6594
|
-
freedBytes += skill.sizeBytes;
|
|
6595
|
-
}
|
|
6596
|
-
return { removed, freedBytes };
|
|
6597
|
-
}
|
|
6598
|
-
function getDirectorySize(dirPath) {
|
|
6599
|
-
let totalSize = 0;
|
|
6600
|
-
try {
|
|
6601
|
-
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
6602
|
-
for (const entry of entries) {
|
|
6603
|
-
const fullPath = path2.join(dirPath, entry.name);
|
|
6604
|
-
if (entry.isDirectory()) {
|
|
6605
|
-
totalSize += getDirectorySize(fullPath);
|
|
6606
|
-
} else if (entry.isFile()) {
|
|
6607
|
-
try {
|
|
6608
|
-
const stats = statSync(fullPath);
|
|
6609
|
-
totalSize += stats.size;
|
|
6610
|
-
} catch {
|
|
6611
|
-
}
|
|
6612
|
-
}
|
|
6613
|
-
}
|
|
6614
|
-
} catch {
|
|
6615
|
-
}
|
|
6616
|
-
return totalSize;
|
|
6617
|
-
}
|
|
6618
|
-
function formatBytes(bytes) {
|
|
6619
|
-
if (bytes === 0) {
|
|
6620
|
-
return "0 B";
|
|
6621
|
-
}
|
|
6622
|
-
const units = ["B", "KB", "MB", "GB"];
|
|
6623
|
-
const k = 1024;
|
|
6624
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
6625
|
-
const size = bytes / Math.pow(k, i);
|
|
6626
|
-
return `${size.toFixed(1)} ${units[i]}`;
|
|
6627
|
-
}
|
|
6628
|
-
function compareVersions(a, b) {
|
|
6629
|
-
const partsA = a.split(".").map(Number);
|
|
6630
|
-
const partsB = b.split(".").map(Number);
|
|
6631
|
-
for (let i = 0; i < 3; i++) {
|
|
6632
|
-
const partA = partsA[i] || 0;
|
|
6633
|
-
const partB = partsB[i] || 0;
|
|
6634
|
-
if (partA !== partB) {
|
|
6635
|
-
return partA - partB;
|
|
6636
|
-
}
|
|
6637
|
-
}
|
|
6638
|
-
return 0;
|
|
6639
|
-
}
|
|
6640
|
-
function listInstalledVersions() {
|
|
6641
|
-
const baseDir = getElectronAppBaseDir();
|
|
6642
|
-
const currentVersion = getElectronAppVersion();
|
|
6643
|
-
const versions = [];
|
|
6644
|
-
if (!existsSync(baseDir)) {
|
|
6645
|
-
return versions;
|
|
6646
|
-
}
|
|
6647
|
-
try {
|
|
6648
|
-
const entries = readdirSync(baseDir, { withFileTypes: true });
|
|
6649
|
-
for (const entry of entries) {
|
|
6650
|
-
if (!entry.isDirectory()) {
|
|
6651
|
-
continue;
|
|
6652
|
-
}
|
|
6653
|
-
if (!/^\d+\.\d+\.\d+$/.test(entry.name)) {
|
|
6654
|
-
continue;
|
|
6655
|
-
}
|
|
6656
|
-
const versionPath = path2.join(baseDir, entry.name);
|
|
6657
|
-
const sizeBytes = getDirectorySize(versionPath);
|
|
6658
|
-
versions.push({
|
|
6659
|
-
version: entry.name,
|
|
6660
|
-
path: versionPath,
|
|
6661
|
-
sizeBytes,
|
|
6662
|
-
isCurrent: entry.name === currentVersion
|
|
6663
|
-
});
|
|
6664
|
-
}
|
|
6665
|
-
} catch (error) {
|
|
6666
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6667
|
-
logger7.warn("Failed to list installed versions", { error: errorMessage });
|
|
6668
|
-
}
|
|
6669
|
-
versions.sort((a, b) => compareVersions(b.version, a.version));
|
|
6670
|
-
return versions;
|
|
6671
|
-
}
|
|
6672
|
-
function cleanupOldVersions(options = {}) {
|
|
6673
|
-
const { all = false, dryRun = false } = options;
|
|
6674
|
-
const versions = listInstalledVersions();
|
|
6675
|
-
const removed = [];
|
|
6676
|
-
let freedBytes = 0;
|
|
6677
|
-
const versionsToKeep = all ? 1 : 2;
|
|
6678
|
-
let keptCount = 0;
|
|
6679
|
-
for (const version of versions) {
|
|
6680
|
-
if (version.isCurrent) {
|
|
6681
|
-
keptCount++;
|
|
6682
|
-
continue;
|
|
6683
|
-
}
|
|
6684
|
-
if (keptCount < versionsToKeep) {
|
|
6685
|
-
keptCount++;
|
|
6686
|
-
continue;
|
|
6687
|
-
}
|
|
6688
|
-
if (!dryRun) {
|
|
6689
|
-
try {
|
|
6690
|
-
rmSync(version.path, { recursive: true, force: true });
|
|
6691
|
-
logger7.info("Removed old version", {
|
|
6692
|
-
version: version.version,
|
|
6693
|
-
freedBytes: version.sizeBytes
|
|
6694
|
-
});
|
|
6695
|
-
} catch (error) {
|
|
6696
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6697
|
-
logger7.error("Failed to remove version", {
|
|
6698
|
-
version: version.version,
|
|
6699
|
-
error: errorMessage
|
|
6700
|
-
});
|
|
6701
|
-
continue;
|
|
6702
|
-
}
|
|
6703
|
-
}
|
|
6704
|
-
removed.push(version);
|
|
6705
|
-
freedBytes += version.sizeBytes;
|
|
6706
|
-
}
|
|
6707
|
-
return { removed, freedBytes };
|
|
6708
|
-
}
|
|
6709
|
-
async function versionsCommand() {
|
|
6710
|
-
console.log("\nInstalled Electron App Versions");
|
|
6711
|
-
console.log("================================\n");
|
|
6712
|
-
const versions = listInstalledVersions();
|
|
6713
|
-
if (versions.length === 0) {
|
|
6714
|
-
console.log("No versions installed.");
|
|
6715
|
-
console.log("Run 'muggle setup' to download the Electron app.\n");
|
|
6716
|
-
return;
|
|
6717
|
-
}
|
|
6718
|
-
let totalSize = 0;
|
|
6719
|
-
for (const version of versions) {
|
|
6720
|
-
const marker = version.isCurrent ? " (current)" : "";
|
|
6721
|
-
const size = formatBytes(version.sizeBytes);
|
|
6722
|
-
console.log(` v${version.version}${marker} - ${size}`);
|
|
6723
|
-
totalSize += version.sizeBytes;
|
|
6724
|
-
}
|
|
6725
|
-
console.log("");
|
|
6726
|
-
console.log(`Total: ${versions.length} version(s), ${formatBytes(totalSize)}`);
|
|
6727
|
-
console.log("");
|
|
6728
|
-
}
|
|
6729
|
-
async function cleanupCommand(options) {
|
|
6730
|
-
let totalFreedBytes = 0;
|
|
6731
|
-
let totalRemovedCount = 0;
|
|
6732
|
-
console.log("\nElectron App Cleanup");
|
|
6733
|
-
console.log("====================\n");
|
|
6734
|
-
const versions = listInstalledVersions();
|
|
6735
|
-
if (versions.length === 0) {
|
|
6736
|
-
console.log("No versions installed. Nothing to clean up.\n");
|
|
6737
|
-
} else if (versions.length === 1) {
|
|
6738
|
-
console.log("Only the current version is installed. Nothing to clean up.\n");
|
|
6739
|
-
} else {
|
|
6740
|
-
const currentVersion = versions.find((v) => v.isCurrent);
|
|
6741
|
-
const oldVersions = versions.filter((v) => !v.isCurrent);
|
|
6742
|
-
console.log(`Current version: v${currentVersion?.version ?? "unknown"}`);
|
|
6743
|
-
console.log(`Old versions: ${oldVersions.length}`);
|
|
6744
|
-
console.log("");
|
|
6745
|
-
if (options.dryRun) {
|
|
6746
|
-
console.log("Dry run - showing what would be deleted:\n");
|
|
6747
|
-
}
|
|
6748
|
-
const result = cleanupOldVersions(options);
|
|
6749
|
-
if (result.removed.length === 0) {
|
|
6750
|
-
if (options.all) {
|
|
6751
|
-
console.log("No old versions to remove.\n");
|
|
6752
|
-
} else {
|
|
6753
|
-
console.log("Keeping one previous version for rollback.");
|
|
6754
|
-
console.log("Use --all to remove all old versions.\n");
|
|
6755
|
-
}
|
|
6756
|
-
} else {
|
|
6757
|
-
console.log(options.dryRun ? "Would remove:" : "Removed:");
|
|
6758
|
-
for (const version of result.removed) {
|
|
6759
|
-
console.log(` v${version.version} (${formatBytes(version.sizeBytes)})`);
|
|
6760
|
-
}
|
|
6761
|
-
totalFreedBytes += result.freedBytes;
|
|
6762
|
-
totalRemovedCount += result.removed.length;
|
|
6763
|
-
console.log("");
|
|
6764
|
-
}
|
|
6765
|
-
}
|
|
6766
|
-
if (options.skills) {
|
|
6767
|
-
console.log("Skills Cleanup");
|
|
6768
|
-
console.log("==============\n");
|
|
6769
|
-
const obsoleteSkills = listObsoleteSkills();
|
|
6770
|
-
if (obsoleteSkills.length === 0) {
|
|
6771
|
-
console.log("No obsolete skills found. Nothing to clean up.\n");
|
|
6772
|
-
} else {
|
|
6773
|
-
console.log(`Found ${obsoleteSkills.length} obsolete skill(s):
|
|
6774
|
-
`);
|
|
6775
|
-
if (options.dryRun) {
|
|
6776
|
-
console.log("Dry run - showing what would be deleted:\n");
|
|
6777
|
-
}
|
|
6778
|
-
const skillResult = cleanupObsoleteSkills({ dryRun: options.dryRun });
|
|
6779
|
-
console.log(options.dryRun ? "Would remove:" : "Removed:");
|
|
6780
|
-
for (const skill of skillResult.removed) {
|
|
6781
|
-
console.log(` ${skill.name} (${formatBytes(skill.sizeBytes)})`);
|
|
6782
|
-
}
|
|
6783
|
-
totalFreedBytes += skillResult.freedBytes;
|
|
6784
|
-
totalRemovedCount += skillResult.removed.length;
|
|
6785
|
-
console.log("");
|
|
6786
|
-
}
|
|
6787
|
-
}
|
|
6788
|
-
if (totalRemovedCount > 0) {
|
|
6789
|
-
console.log(
|
|
6790
|
-
`${options.dryRun ? "Would free" : "Freed"}: ${formatBytes(totalFreedBytes)} total`
|
|
6791
|
-
);
|
|
6792
|
-
console.log("");
|
|
6793
|
-
if (options.dryRun) {
|
|
6794
|
-
console.log("Run without --dry-run to actually delete.\n");
|
|
6795
|
-
}
|
|
6796
|
-
}
|
|
6797
|
-
logger7.info("Cleanup completed", {
|
|
6798
|
-
removed: totalRemovedCount,
|
|
6799
|
-
freedBytes: totalFreedBytes,
|
|
6800
|
-
dryRun: options.dryRun,
|
|
6801
|
-
includeSkills: options.skills
|
|
6802
|
-
});
|
|
6803
|
-
}
|
|
6804
|
-
var logger8 = getLogger();
|
|
6805
|
-
function getCursorMcpConfigPath() {
|
|
6806
|
-
return join(homedir(), ".cursor", "mcp.json");
|
|
6807
|
-
}
|
|
6808
|
-
function getExpectedExecutablePath(versionDir) {
|
|
6809
|
-
const os4 = platform();
|
|
6810
|
-
switch (os4) {
|
|
6811
|
-
case "darwin":
|
|
6812
|
-
return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
6813
|
-
case "win32":
|
|
6814
|
-
return path2.join(versionDir, "MuggleAI.exe");
|
|
6815
|
-
case "linux":
|
|
6816
|
-
return path2.join(versionDir, "MuggleAI");
|
|
6817
|
-
default:
|
|
6818
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
6819
|
-
}
|
|
6820
|
-
}
|
|
6821
|
-
function verifyElectronAppInstallation() {
|
|
6822
|
-
const version = getElectronAppVersion();
|
|
6823
|
-
const versionDir = getElectronAppDir(version);
|
|
6824
|
-
const executablePath = getExpectedExecutablePath(versionDir);
|
|
6825
|
-
const metadataPath = path2.join(versionDir, ".install-metadata.json");
|
|
6826
|
-
const result = {
|
|
6827
|
-
valid: false,
|
|
6828
|
-
versionDir,
|
|
6829
|
-
executablePath,
|
|
6830
|
-
executableExists: false,
|
|
6831
|
-
executableIsFile: false,
|
|
6832
|
-
metadataExists: false,
|
|
6833
|
-
hasPartialArchive: false
|
|
6834
|
-
};
|
|
6835
|
-
if (!fs3.existsSync(versionDir)) {
|
|
6836
|
-
result.errorDetail = "Version directory does not exist";
|
|
6837
|
-
return result;
|
|
6838
|
-
}
|
|
6839
|
-
const archivePatterns = ["MuggleAI-darwin", "MuggleAI-win32", "MuggleAI-linux"];
|
|
6840
|
-
try {
|
|
6841
|
-
const files = fs3.readdirSync(versionDir);
|
|
6842
|
-
for (const file of files) {
|
|
6843
|
-
if (archivePatterns.some((pattern) => file.startsWith(pattern)) && (file.endsWith(".zip") || file.endsWith(".tar.gz"))) {
|
|
6844
|
-
result.hasPartialArchive = true;
|
|
6845
|
-
break;
|
|
6846
|
-
}
|
|
6847
|
-
}
|
|
6848
|
-
} catch {
|
|
6849
|
-
}
|
|
6850
|
-
result.executableExists = fs3.existsSync(executablePath);
|
|
6851
|
-
if (!result.executableExists) {
|
|
6852
|
-
if (result.hasPartialArchive) {
|
|
6853
|
-
result.errorDetail = "Download incomplete: archive found but not extracted";
|
|
6854
|
-
} else {
|
|
6855
|
-
result.errorDetail = "Executable not found at expected path";
|
|
6856
|
-
}
|
|
6857
|
-
return result;
|
|
6858
|
-
}
|
|
6859
|
-
try {
|
|
6860
|
-
const stats = fs3.statSync(executablePath);
|
|
6861
|
-
result.executableIsFile = stats.isFile();
|
|
6862
|
-
if (!result.executableIsFile) {
|
|
6863
|
-
result.errorDetail = "Executable path exists but is not a file";
|
|
6864
|
-
return result;
|
|
6865
|
-
}
|
|
6866
|
-
} catch {
|
|
6867
|
-
result.errorDetail = "Cannot stat executable (broken symlink?)";
|
|
6868
|
-
return result;
|
|
6869
|
-
}
|
|
6870
|
-
result.metadataExists = fs3.existsSync(metadataPath);
|
|
6871
|
-
result.valid = true;
|
|
6872
|
-
return result;
|
|
6873
|
-
}
|
|
6874
|
-
function validateCursorMcpConfig() {
|
|
6875
|
-
const cursorMcpConfigPath = getCursorMcpConfigPath();
|
|
6876
|
-
if (!existsSync(cursorMcpConfigPath)) {
|
|
6877
|
-
return {
|
|
6878
|
-
passed: false,
|
|
6879
|
-
description: `Missing at ${cursorMcpConfigPath}`
|
|
6880
|
-
};
|
|
6881
|
-
}
|
|
6882
|
-
try {
|
|
6883
|
-
const rawCursorConfig = JSON.parse(
|
|
6884
|
-
readFileSync(cursorMcpConfigPath, "utf-8")
|
|
6885
|
-
);
|
|
6886
|
-
if (!rawCursorConfig.mcpServers) {
|
|
6887
|
-
return {
|
|
6888
|
-
passed: false,
|
|
6889
|
-
description: "Missing mcpServers key"
|
|
6890
|
-
};
|
|
6891
|
-
}
|
|
6892
|
-
const muggleServerConfig = rawCursorConfig.mcpServers.muggle;
|
|
6893
|
-
if (!muggleServerConfig) {
|
|
6894
|
-
return {
|
|
6895
|
-
passed: false,
|
|
6896
|
-
description: "Missing mcpServers.muggle entry"
|
|
6897
|
-
};
|
|
6898
|
-
}
|
|
6899
|
-
if (!Array.isArray(muggleServerConfig.args)) {
|
|
6900
|
-
return {
|
|
6901
|
-
passed: false,
|
|
6902
|
-
description: "mcpServers.muggle.args is not an array"
|
|
6903
|
-
};
|
|
6904
|
-
}
|
|
6905
|
-
const hasServeArgument = muggleServerConfig.args.includes("serve");
|
|
6906
|
-
if (!hasServeArgument) {
|
|
6907
|
-
return {
|
|
6908
|
-
passed: false,
|
|
6909
|
-
description: "mcpServers.muggle args does not include 'serve'"
|
|
6910
|
-
};
|
|
6911
|
-
}
|
|
6912
|
-
if (muggleServerConfig.command === "node") {
|
|
6913
|
-
const firstArgument = muggleServerConfig.args.at(0);
|
|
6914
|
-
if (!firstArgument) {
|
|
6915
|
-
return {
|
|
6916
|
-
passed: false,
|
|
6917
|
-
description: "mcpServers.muggle command is node but args[0] is missing"
|
|
6918
|
-
};
|
|
6919
|
-
}
|
|
6920
|
-
if (!existsSync(firstArgument)) {
|
|
6921
|
-
return {
|
|
6922
|
-
passed: false,
|
|
6923
|
-
description: `mcpServers.muggle args[0] does not exist: ${firstArgument}`
|
|
6924
|
-
};
|
|
6925
|
-
}
|
|
6926
|
-
}
|
|
6927
|
-
return {
|
|
6928
|
-
passed: true,
|
|
6929
|
-
description: `Configured at ${cursorMcpConfigPath}`
|
|
6930
|
-
};
|
|
6931
|
-
} catch (error) {
|
|
6932
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6933
|
-
return {
|
|
6934
|
-
passed: false,
|
|
6935
|
-
description: `Invalid JSON or schema: ${errorMessage}`
|
|
6936
|
-
};
|
|
6937
|
-
}
|
|
6938
|
-
}
|
|
6939
|
-
function runDiagnostics() {
|
|
6940
|
-
const results = [];
|
|
6941
|
-
const config = getConfig();
|
|
6942
|
-
const dataDir = getDataDir2();
|
|
6943
|
-
results.push({
|
|
6944
|
-
name: "Data Directory",
|
|
6945
|
-
passed: existsSync(dataDir),
|
|
6946
|
-
description: existsSync(dataDir) ? `Found at ${dataDir}` : `Not found at ${dataDir}`,
|
|
6947
|
-
suggestion: "Run 'muggle login' to create the data directory"
|
|
6948
|
-
});
|
|
6949
|
-
const electronVersion = getElectronAppVersion();
|
|
6950
|
-
const bundledVersion = getBundledElectronAppVersion();
|
|
6951
|
-
const versionSource = getElectronAppVersionSource();
|
|
6952
|
-
const installVerification = verifyElectronAppInstallation();
|
|
6953
|
-
let electronDescription;
|
|
6954
|
-
let electronSuggestion;
|
|
6955
|
-
if (installVerification.valid) {
|
|
6956
|
-
electronDescription = `Installed (v${electronVersion})`;
|
|
6957
|
-
switch (versionSource) {
|
|
6958
|
-
case "env":
|
|
6959
|
-
electronDescription += ` [from ELECTRON_APP_VERSION env]`;
|
|
6960
|
-
break;
|
|
6961
|
-
case "override":
|
|
6962
|
-
electronDescription += ` [overridden from bundled v${bundledVersion}]`;
|
|
6963
|
-
break;
|
|
6964
|
-
}
|
|
6965
|
-
if (!installVerification.metadataExists) {
|
|
6966
|
-
electronDescription += " [missing metadata]";
|
|
6967
|
-
}
|
|
6968
|
-
} else {
|
|
6969
|
-
electronDescription = `Not installed (expected v${electronVersion})`;
|
|
6970
|
-
if (installVerification.errorDetail) {
|
|
6971
|
-
electronDescription += `
|
|
6972
|
-
\u2514\u2500 ${installVerification.errorDetail}`;
|
|
6973
|
-
electronDescription += `
|
|
6974
|
-
\u2514\u2500 Checked: ${installVerification.versionDir}`;
|
|
6975
|
-
}
|
|
6976
|
-
if (installVerification.hasPartialArchive) {
|
|
6977
|
-
electronSuggestion = "Run 'muggle setup --force' to re-download and extract";
|
|
6978
|
-
} else {
|
|
6979
|
-
electronSuggestion = "Run 'muggle setup' to download the Electron app";
|
|
6980
|
-
}
|
|
6981
|
-
}
|
|
6982
|
-
results.push({
|
|
6983
|
-
name: "Electron App",
|
|
6984
|
-
passed: installVerification.valid,
|
|
6985
|
-
description: electronDescription,
|
|
6986
|
-
suggestion: electronSuggestion
|
|
6987
|
-
});
|
|
6988
|
-
if (installVerification.valid) {
|
|
6989
|
-
results.push({
|
|
6990
|
-
name: "Electron App Updates",
|
|
6991
|
-
passed: true,
|
|
6992
|
-
description: "Run 'muggle upgrade --check' to check for updates"
|
|
6993
|
-
});
|
|
6994
|
-
}
|
|
6995
|
-
const authService = getAuthService();
|
|
6996
|
-
const authStatus = authService.getAuthStatus();
|
|
6997
|
-
results.push({
|
|
6998
|
-
name: "Authentication",
|
|
6999
|
-
passed: authStatus.authenticated,
|
|
7000
|
-
description: authStatus.authenticated ? `Authenticated as ${authStatus.email ?? "unknown"}` : "Not authenticated",
|
|
7001
|
-
suggestion: "Run 'muggle login' to authenticate"
|
|
7002
|
-
});
|
|
7003
|
-
const hasStoredApiKey = hasApiKey();
|
|
7004
|
-
results.push({
|
|
7005
|
-
name: "API Key",
|
|
7006
|
-
passed: hasStoredApiKey,
|
|
7007
|
-
description: hasStoredApiKey ? "API key stored" : "No API key stored (optional)",
|
|
7008
|
-
suggestion: "Run 'muggle login --key-name <name>' to generate an API key"
|
|
7009
|
-
});
|
|
7010
|
-
const credentialsPath = getCredentialsFilePath();
|
|
7011
|
-
results.push({
|
|
7012
|
-
name: "Credentials File",
|
|
7013
|
-
passed: existsSync(credentialsPath),
|
|
7014
|
-
description: existsSync(credentialsPath) ? `Found at ${credentialsPath}` : `Not found at ${credentialsPath}`,
|
|
7015
|
-
suggestion: "Run 'muggle login' to create credentials"
|
|
7016
|
-
});
|
|
7017
|
-
results.push({
|
|
7018
|
-
name: "Prompt Service URL",
|
|
7019
|
-
passed: !!config.e2e.promptServiceBaseUrl,
|
|
7020
|
-
description: config.e2e.promptServiceBaseUrl
|
|
7021
|
-
});
|
|
7022
|
-
results.push({
|
|
7023
|
-
name: "Web Service URL",
|
|
7024
|
-
passed: !!config.localQa.webServiceUrl,
|
|
7025
|
-
description: config.localQa.webServiceUrl
|
|
7026
|
-
});
|
|
7027
|
-
const cursorMcpConfigValidationResult = validateCursorMcpConfig();
|
|
7028
|
-
results.push({
|
|
7029
|
-
name: "Cursor MCP Config",
|
|
7030
|
-
passed: cursorMcpConfigValidationResult.passed,
|
|
7031
|
-
description: cursorMcpConfigValidationResult.description,
|
|
7032
|
-
suggestion: "Re-run npm install -g @muggleai/works to refresh ~/.cursor/mcp.json"
|
|
7033
|
-
});
|
|
7034
|
-
return results;
|
|
7035
|
-
}
|
|
7036
|
-
function formatCheckResult(result) {
|
|
7037
|
-
const icon = result.passed ? "\u2713" : "\u2717";
|
|
7038
|
-
const color = result.passed ? "\x1B[32m" : "\x1B[31m";
|
|
7039
|
-
const reset = "\x1B[0m";
|
|
7040
|
-
let output = `${color}${icon}${reset} ${result.name}: ${result.description}`;
|
|
7041
|
-
if (!result.passed && result.suggestion) {
|
|
7042
|
-
output += `
|
|
7043
|
-
\u2514\u2500 ${result.suggestion}`;
|
|
7044
|
-
}
|
|
7045
|
-
return output;
|
|
7046
|
-
}
|
|
7047
|
-
async function doctorCommand() {
|
|
7048
|
-
console.log("\nMuggle Works Doctor");
|
|
7049
|
-
console.log("=================\n");
|
|
7050
|
-
const results = runDiagnostics();
|
|
7051
|
-
for (const result of results) {
|
|
7052
|
-
console.log(formatCheckResult(result));
|
|
7053
|
-
}
|
|
7054
|
-
console.log("");
|
|
7055
|
-
const failedCount = results.filter((r) => !r.passed).length;
|
|
7056
|
-
if (failedCount === 0) {
|
|
7057
|
-
console.log("All checks passed! Your installation is ready.");
|
|
7058
|
-
} else {
|
|
7059
|
-
console.log(`${failedCount} issue(s) found. See suggestions above.`);
|
|
7060
|
-
}
|
|
7061
|
-
logger8.info("Doctor command completed", {
|
|
7062
|
-
totalChecks: results.length,
|
|
7063
|
-
passed: results.length - failedCount,
|
|
7064
|
-
failed: failedCount
|
|
7065
|
-
});
|
|
7066
|
-
}
|
|
7067
|
-
|
|
7068
|
-
// src/cli/help.ts
|
|
7069
|
-
var COLORS = {
|
|
7070
|
-
reset: "\x1B[0m",
|
|
7071
|
-
bold: "\x1B[1m",
|
|
7072
|
-
dim: "\x1B[2m",
|
|
7073
|
-
cyan: "\x1B[36m",
|
|
7074
|
-
green: "\x1B[32m",
|
|
7075
|
-
yellow: "\x1B[33m",
|
|
7076
|
-
blue: "\x1B[34m"};
|
|
7077
|
-
function colorize(text, color) {
|
|
7078
|
-
if (process.env.NO_COLOR) {
|
|
7079
|
-
return text;
|
|
7080
|
-
}
|
|
7081
|
-
return `${color}${text}${COLORS.reset}`;
|
|
7082
|
-
}
|
|
7083
|
-
function header(title) {
|
|
7084
|
-
return colorize(`
|
|
7085
|
-
${title}`, COLORS.bold + COLORS.cyan);
|
|
7086
|
-
}
|
|
7087
|
-
function cmd(cmd2) {
|
|
7088
|
-
return colorize(cmd2, COLORS.green);
|
|
7089
|
-
}
|
|
7090
|
-
function path12(path15) {
|
|
7091
|
-
return colorize(path15, COLORS.yellow);
|
|
7092
|
-
}
|
|
7093
|
-
function getHelpGuidance() {
|
|
7094
|
-
const lines = [
|
|
7095
|
-
"",
|
|
7096
|
-
colorize("=".repeat(70), COLORS.cyan),
|
|
7097
|
-
colorize(" Muggle AI Works - Comprehensive How-To Guide", COLORS.bold + COLORS.green),
|
|
7098
|
-
colorize("=".repeat(70), COLORS.cyan),
|
|
7099
|
-
"",
|
|
7100
|
-
header("What is Muggle AI Works?"),
|
|
7101
|
-
"",
|
|
7102
|
-
" Muggle AI Works is a Model Context Protocol server that provides AI",
|
|
7103
|
-
" assistants with tools to perform automated end-to-end (E2E) acceptance testing of web applications.",
|
|
7104
|
-
"",
|
|
7105
|
-
" It supports both:",
|
|
7106
|
-
` ${colorize("\u2022", COLORS.green)} Cloud E2E - Test remote production/staging sites with a public URL`,
|
|
7107
|
-
` ${colorize("\u2022", COLORS.green)} Local E2E - Test localhost development servers`,
|
|
7108
|
-
"",
|
|
7109
|
-
header("Setup Instructions"),
|
|
7110
|
-
"",
|
|
7111
|
-
` ${colorize("Step 1:", COLORS.bold)} Configure your MCP client`,
|
|
7112
|
-
"",
|
|
7113
|
-
` For ${colorize("Cursor", COLORS.bold)}, edit ${path12("~/.cursor/mcp.json")}:`,
|
|
7114
|
-
"",
|
|
7115
|
-
` ${colorize("{", COLORS.dim)}`,
|
|
7116
|
-
` ${colorize('"mcpServers"', COLORS.yellow)}: {`,
|
|
7117
|
-
` ${colorize('"muggle"', COLORS.yellow)}: {`,
|
|
7118
|
-
` ${colorize('"command"', COLORS.yellow)}: ${colorize('"muggle"', COLORS.green)},`,
|
|
7119
|
-
` ${colorize('"args"', COLORS.yellow)}: [${colorize('"serve"', COLORS.green)}]`,
|
|
7120
|
-
` }`,
|
|
7121
|
-
` }`,
|
|
7122
|
-
` ${colorize("}", COLORS.dim)}`,
|
|
7123
|
-
"",
|
|
7124
|
-
` ${colorize("Step 2:", COLORS.bold)} Restart your MCP client`,
|
|
7125
|
-
"",
|
|
7126
|
-
` ${colorize("Step 3:", COLORS.bold)} Start testing! Ask your AI assistant:`,
|
|
7127
|
-
` ${colorize('"Test the login flow on my app at http://localhost:3000"', COLORS.dim)}`,
|
|
7128
|
-
"",
|
|
7129
|
-
header("CLI Commands"),
|
|
7130
|
-
"",
|
|
7131
|
-
` ${colorize("Server Commands:", COLORS.bold)}`,
|
|
7132
|
-
` ${cmd("muggle serve")} Start MCP server with all tools`,
|
|
7133
|
-
` ${cmd("muggle serve --e2e")} Start with cloud E2E tools only`,
|
|
7134
|
-
` ${cmd("muggle serve --local")} Start with local E2E tools only`,
|
|
7135
|
-
"",
|
|
7136
|
-
` ${colorize("Setup & Diagnostics:", COLORS.bold)}`,
|
|
7137
|
-
` ${cmd("muggle setup")} Download/update Electron app`,
|
|
7138
|
-
` ${cmd("muggle setup --force")} Force re-download`,
|
|
7139
|
-
` ${cmd("muggle doctor")} Diagnose installation issues`,
|
|
7140
|
-
` ${cmd("muggle upgrade")} Check for updates`,
|
|
7141
|
-
` ${cmd("muggle upgrade --check")} Check updates without installing`,
|
|
7142
|
-
"",
|
|
7143
|
-
` ${colorize("Authentication:", COLORS.bold)}`,
|
|
7144
|
-
` ${cmd("muggle login")} Login to Muggle AI`,
|
|
7145
|
-
` ${cmd("muggle logout")} Clear stored credentials`,
|
|
7146
|
-
` ${cmd("muggle status")} Show authentication status`,
|
|
7147
|
-
"",
|
|
7148
|
-
` ${colorize("Maintenance:", COLORS.bold)}`,
|
|
7149
|
-
` ${cmd("muggle versions")} List installed Electron app versions`,
|
|
7150
|
-
` ${cmd("muggle cleanup")} Remove old Electron app versions`,
|
|
7151
|
-
` ${cmd("muggle cleanup --all")} Remove all old versions`,
|
|
7152
|
-
` ${cmd("muggle cleanup --skills")} Also remove obsolete skills`,
|
|
7153
|
-
"",
|
|
7154
|
-
` ${colorize("Help:", COLORS.bold)}`,
|
|
7155
|
-
` ${cmd("muggle help")} Show this guide`,
|
|
7156
|
-
` ${cmd("muggle --help")} Show command synopsis`,
|
|
7157
|
-
` ${cmd("muggle --version")} Show version`,
|
|
7158
|
-
"",
|
|
7159
|
-
header("Authentication Flow"),
|
|
7160
|
-
"",
|
|
7161
|
-
" Authentication happens automatically when you first use a tool that",
|
|
7162
|
-
" requires it:",
|
|
7163
|
-
"",
|
|
7164
|
-
` 1. ${colorize("A browser window opens", COLORS.bold)} with a verification code`,
|
|
7165
|
-
` 2. ${colorize("Log in", COLORS.bold)} with your Muggle AI account`,
|
|
7166
|
-
` 3. ${colorize("The tool call continues", COLORS.bold)} with your credentials`,
|
|
7167
|
-
"",
|
|
7168
|
-
` API keys are stored in ${path12("~/.muggle-ai/api-key.json")}`,
|
|
7169
|
-
"",
|
|
7170
|
-
header("Available MCP Tools"),
|
|
7171
|
-
"",
|
|
7172
|
-
` ${colorize("Cloud E2E tools:", COLORS.bold)} (prefix: muggle-remote-)`,
|
|
7173
|
-
" muggle-remote-project-create, muggle-remote-project-list, muggle-remote-use-case-create-from-prompts,",
|
|
7174
|
-
" muggle-remote-test-case-generate-from-prompt, muggle-remote-workflow-start-*, etc.",
|
|
7175
|
-
"",
|
|
7176
|
-
` ${colorize("Local E2E tools:", COLORS.bold)} (prefix: muggle-local-)`,
|
|
7177
|
-
" muggle-local-execute-test-generation, muggle-local-execute-replay,",
|
|
7178
|
-
" muggle-local-publish-test-script, muggle-local-run-result-get,",
|
|
7179
|
-
" muggle-local-check-status, etc.",
|
|
7180
|
-
"",
|
|
7181
|
-
header("Data Directory"),
|
|
7182
|
-
"",
|
|
7183
|
-
` All data is stored in ${path12("~/.muggle-ai/")}:`,
|
|
7184
|
-
"",
|
|
7185
|
-
` ${path12("api-key.json")} Long-lived API key (auto-generated)`,
|
|
7186
|
-
` ${path12("projects/")} Local test projects`,
|
|
7187
|
-
` ${path12("sessions/")} Test execution sessions`,
|
|
7188
|
-
` ${path12("electron-app/")} Downloaded Electron app binaries`,
|
|
7189
|
-
"",
|
|
7190
|
-
header("Troubleshooting"),
|
|
7191
|
-
"",
|
|
7192
|
-
` ${colorize("Installation issues:", COLORS.bold)}`,
|
|
7193
|
-
` Run ${cmd("muggle doctor")} to diagnose problems`,
|
|
7194
|
-
"",
|
|
7195
|
-
` ${colorize("Electron app not found:", COLORS.bold)}`,
|
|
7196
|
-
` Run ${cmd("muggle setup --force")} to re-download`,
|
|
7197
|
-
"",
|
|
7198
|
-
` ${colorize("Authentication issues:", COLORS.bold)}`,
|
|
7199
|
-
` Run ${cmd("muggle logout")} then ${cmd("muggle login")}`,
|
|
7200
|
-
"",
|
|
7201
|
-
` ${colorize("MCP not working in client:", COLORS.bold)}`,
|
|
7202
|
-
" 1. Verify mcp.json configuration",
|
|
7203
|
-
" 2. Restart your MCP client",
|
|
7204
|
-
` 3. Check ${cmd("muggle doctor")} output`,
|
|
7205
|
-
"",
|
|
7206
|
-
header("Documentation & Support"),
|
|
7207
|
-
"",
|
|
7208
|
-
` Docs: ${colorize("https://www.muggle-ai.com/muggleTestV0/docs/mcp/mcp-overview", COLORS.blue)}`,
|
|
7209
|
-
` GitHub: ${colorize("https://github.com/multiplex-ai/muggle-ai-works", COLORS.blue)}`,
|
|
7210
|
-
"",
|
|
7211
|
-
colorize("=".repeat(70), COLORS.cyan),
|
|
7212
|
-
""
|
|
7213
|
-
];
|
|
7214
|
-
return lines.join("\n");
|
|
7215
|
-
}
|
|
7216
|
-
function helpCommand() {
|
|
7217
|
-
console.log(getHelpGuidance());
|
|
7218
|
-
}
|
|
7219
|
-
|
|
7220
|
-
// src/cli/login.ts
|
|
7221
|
-
var logger9 = getLogger();
|
|
7222
|
-
async function loginCommand(options) {
|
|
7223
|
-
console.log("\nMuggle AI Login");
|
|
7224
|
-
console.log("===============\n");
|
|
7225
|
-
const expiry = options.keyExpiry || "90d";
|
|
7226
|
-
console.log("Starting device code authentication...");
|
|
7227
|
-
console.log("A browser window will open for you to complete login.\n");
|
|
7228
|
-
const result = await performLogin(options.keyName, expiry);
|
|
7229
|
-
if (result.success) {
|
|
7230
|
-
console.log("\u2713 Login successful!");
|
|
7231
|
-
if (result.credentials?.email) {
|
|
7232
|
-
console.log(` Logged in as: ${result.credentials.email}`);
|
|
7233
|
-
}
|
|
7234
|
-
if (result.credentials?.apiKey) {
|
|
7235
|
-
console.log(" API key created and stored for future use.");
|
|
7236
|
-
}
|
|
7237
|
-
console.log("\nYou can now use Muggle AI Works tools.");
|
|
7238
|
-
} else {
|
|
7239
|
-
console.error("\u2717 Login failed");
|
|
7240
|
-
if (result.error) {
|
|
7241
|
-
console.error(` Error: ${result.error}`);
|
|
7242
|
-
}
|
|
7243
|
-
if (result.deviceCodeResponse) {
|
|
7244
|
-
console.log("\nIf browser didn't open, visit:");
|
|
7245
|
-
console.log(` ${result.deviceCodeResponse.verificationUriComplete}`);
|
|
7246
|
-
console.log(` Code: ${result.deviceCodeResponse.userCode}`);
|
|
7247
|
-
}
|
|
7248
|
-
process.exit(1);
|
|
7249
|
-
}
|
|
7250
|
-
}
|
|
7251
|
-
async function logoutCommand() {
|
|
7252
|
-
console.log("\nLogging out...");
|
|
7253
|
-
performLogout();
|
|
7254
|
-
console.log("\u2713 Credentials cleared successfully.");
|
|
7255
|
-
logger9.info("Logout completed");
|
|
7256
|
-
}
|
|
7257
|
-
async function statusCommand() {
|
|
7258
|
-
console.log("\nAuthentication Status");
|
|
7259
|
-
console.log("=====================\n");
|
|
7260
|
-
const authService = getAuthService();
|
|
7261
|
-
const status = authService.getAuthStatus();
|
|
7262
|
-
const hasStoredApiKey = hasApiKey();
|
|
7263
|
-
if (status.authenticated) {
|
|
7264
|
-
console.log("\u2713 Authenticated");
|
|
7265
|
-
if (status.email) {
|
|
7266
|
-
console.log(` Email: ${status.email}`);
|
|
7267
|
-
}
|
|
7268
|
-
if (status.userId) {
|
|
7269
|
-
console.log(` User ID: ${status.userId}`);
|
|
7270
|
-
}
|
|
7271
|
-
if (status.expiresAt) {
|
|
7272
|
-
const expiresDate = new Date(status.expiresAt);
|
|
7273
|
-
console.log(` Token expires: ${expiresDate.toLocaleString()}`);
|
|
7274
|
-
if (status.isExpired) {
|
|
7275
|
-
console.log(" (Token expired - will refresh automatically on next API call)");
|
|
7276
|
-
}
|
|
7277
|
-
}
|
|
7278
|
-
console.log(` API Key: ${hasStoredApiKey ? "Yes" : "No"}`);
|
|
7279
|
-
} else {
|
|
7280
|
-
console.log("\u2717 Not authenticated");
|
|
7281
|
-
console.log("\nRun 'muggle login' to authenticate.");
|
|
7282
|
-
}
|
|
7283
|
-
}
|
|
7284
|
-
|
|
7285
|
-
// src/cli/serve.ts
|
|
7286
|
-
var logger10 = getLogger();
|
|
7287
|
-
async function serveCommand(options) {
|
|
7288
|
-
const config = getConfig();
|
|
7289
|
-
const enableQa = options.local ? false : true;
|
|
7290
|
-
const enableLocal = options.e2e ? false : true;
|
|
7291
|
-
logger10.info("Starting Muggle MCP Server", {
|
|
7292
|
-
version: config.serverVersion,
|
|
7293
|
-
enableQa,
|
|
7294
|
-
enableLocal,
|
|
7295
|
-
transport: "stdio"
|
|
7296
|
-
});
|
|
7297
|
-
try {
|
|
7298
|
-
if (enableQa) {
|
|
7299
|
-
const qaTools = getQaTools();
|
|
7300
|
-
registerTools(qaTools);
|
|
7301
|
-
logger10.info("Registered cloud E2E acceptance tools", { count: qaTools.length });
|
|
7302
|
-
}
|
|
7303
|
-
if (enableLocal) {
|
|
7304
|
-
const localTools = getLocalQaTools();
|
|
7305
|
-
registerTools(localTools);
|
|
7306
|
-
logger10.info("Registered local E2E acceptance tools", { count: localTools.length });
|
|
7307
|
-
}
|
|
7308
|
-
const mcpServer = createUnifiedMcpServer({
|
|
7309
|
-
enableQaTools: enableQa,
|
|
7310
|
-
enableLocalTools: enableLocal
|
|
7311
|
-
});
|
|
7312
|
-
await startStdioServer(mcpServer);
|
|
7313
|
-
logger10.info("MCP server started successfully");
|
|
7314
|
-
} catch (error) {
|
|
7315
|
-
logger10.error("Failed to start MCP server", {
|
|
7316
|
-
error: error instanceof Error ? error.message : String(error),
|
|
7317
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
7318
|
-
});
|
|
7319
|
-
process.exit(1);
|
|
7320
|
-
}
|
|
7321
|
-
}
|
|
7322
|
-
var logger11 = getLogger();
|
|
7323
|
-
var MAX_RETRY_ATTEMPTS = 3;
|
|
7324
|
-
var RETRY_BASE_DELAY_MS = 2e3;
|
|
7325
|
-
var INSTALL_METADATA_FILE_NAME = ".install-metadata.json";
|
|
7326
|
-
function getBinaryName() {
|
|
7327
|
-
const os4 = platform();
|
|
7328
|
-
const architecture = arch();
|
|
7329
|
-
switch (os4) {
|
|
7330
|
-
case "darwin": {
|
|
7331
|
-
const darwinArch = architecture === "arm64" ? "arm64" : "x64";
|
|
7332
|
-
return `MuggleAI-darwin-${darwinArch}.zip`;
|
|
7333
|
-
}
|
|
7334
|
-
case "win32":
|
|
7335
|
-
return "MuggleAI-win32-x64.zip";
|
|
7336
|
-
case "linux":
|
|
7337
|
-
return "MuggleAI-linux-x64.zip";
|
|
7338
|
-
default:
|
|
7339
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
7340
|
-
}
|
|
7341
|
-
}
|
|
7342
|
-
function getExpectedExecutablePath2(versionDir) {
|
|
7343
|
-
const os4 = platform();
|
|
7344
|
-
switch (os4) {
|
|
7345
|
-
case "darwin":
|
|
7346
|
-
return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
7347
|
-
case "win32":
|
|
7348
|
-
return path2.join(versionDir, "MuggleAI.exe");
|
|
7349
|
-
case "linux":
|
|
7350
|
-
return path2.join(versionDir, "MuggleAI");
|
|
7351
|
-
default:
|
|
7352
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
7353
|
-
}
|
|
7354
|
-
}
|
|
7355
|
-
async function extractZip(zipPath, destDir) {
|
|
7356
|
-
return new Promise((resolve4, reject) => {
|
|
7357
|
-
if (platform() === "win32") {
|
|
7358
|
-
execFile(
|
|
7359
|
-
"powershell",
|
|
7360
|
-
["-command", `Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force`],
|
|
7361
|
-
(error) => {
|
|
7362
|
-
if (error) {
|
|
7363
|
-
reject(error);
|
|
7364
|
-
} else {
|
|
7365
|
-
resolve4();
|
|
7366
|
-
}
|
|
7367
|
-
}
|
|
7368
|
-
);
|
|
7369
|
-
} else {
|
|
7370
|
-
execFile("unzip", ["-o", zipPath, "-d", destDir], (error) => {
|
|
7371
|
-
if (error) {
|
|
7372
|
-
reject(error);
|
|
7373
|
-
} else {
|
|
7374
|
-
resolve4();
|
|
7375
|
-
}
|
|
7376
|
-
});
|
|
7377
|
-
}
|
|
7378
|
-
});
|
|
7379
|
-
}
|
|
7380
|
-
async function extractTarGz(tarPath, destDir) {
|
|
7381
|
-
return new Promise((resolve4, reject) => {
|
|
7382
|
-
execFile("tar", ["-xzf", tarPath, "-C", destDir], (error) => {
|
|
7383
|
-
if (error) {
|
|
7384
|
-
reject(error);
|
|
7385
|
-
} else {
|
|
7386
|
-
resolve4();
|
|
7387
|
-
}
|
|
7388
|
-
});
|
|
7389
|
-
});
|
|
7390
|
-
}
|
|
7391
|
-
function sleep2(ms) {
|
|
7392
|
-
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
7393
|
-
}
|
|
7394
|
-
async function downloadWithRetry(downloadUrl, destPath) {
|
|
7395
|
-
let lastError = null;
|
|
7396
|
-
for (let attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
|
|
7397
|
-
try {
|
|
7398
|
-
if (attempt > 1) {
|
|
7399
|
-
const delayMs = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 2);
|
|
7400
|
-
console.log(`Retry attempt ${attempt}/${MAX_RETRY_ATTEMPTS} after ${delayMs}ms delay...`);
|
|
7401
|
-
await sleep2(delayMs);
|
|
7402
|
-
}
|
|
7403
|
-
const response = await fetch(downloadUrl);
|
|
7404
|
-
if (!response.ok) {
|
|
7405
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
7406
|
-
}
|
|
7407
|
-
if (!response.body) {
|
|
7408
|
-
throw new Error("No response body received");
|
|
7409
|
-
}
|
|
7410
|
-
const fileStream = createWriteStream(destPath);
|
|
7411
|
-
await pipeline(response.body, fileStream);
|
|
7412
|
-
return true;
|
|
7413
|
-
} catch (error) {
|
|
7414
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
7415
|
-
console.error(`Download attempt ${attempt} failed: ${lastError.message}`);
|
|
7416
|
-
if (existsSync(destPath)) {
|
|
7417
|
-
rmSync(destPath, { force: true });
|
|
7418
|
-
}
|
|
7419
|
-
}
|
|
7420
|
-
}
|
|
7421
|
-
if (lastError) {
|
|
7422
|
-
throw new Error(`Download failed after ${MAX_RETRY_ATTEMPTS} attempts: ${lastError.message}`);
|
|
7423
|
-
}
|
|
7424
|
-
return false;
|
|
7425
|
-
}
|
|
7426
|
-
function writeInstallMetadata(params) {
|
|
7427
|
-
const metadata = {
|
|
7428
|
-
version: params.version,
|
|
7429
|
-
binaryName: params.binaryName,
|
|
7430
|
-
platformKey: params.platformKey,
|
|
7431
|
-
executableChecksum: params.executableChecksum,
|
|
7432
|
-
expectedArchiveChecksum: params.expectedArchiveChecksum,
|
|
7433
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7434
|
-
};
|
|
7435
|
-
writeFileSync(params.metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
7436
|
-
`, "utf-8");
|
|
7437
|
-
}
|
|
7438
|
-
function cleanupFailedInstall(versionDir) {
|
|
7439
|
-
if (existsSync(versionDir)) {
|
|
7440
|
-
try {
|
|
7441
|
-
rmSync(versionDir, { recursive: true, force: true });
|
|
7442
|
-
} catch {
|
|
7443
|
-
}
|
|
7444
|
-
}
|
|
7445
|
-
}
|
|
7446
|
-
async function setupCommand(options) {
|
|
7447
|
-
const version = getElectronAppVersion();
|
|
7448
|
-
const versionDir = getElectronAppDir(version);
|
|
7449
|
-
const platformKey = getPlatformKey();
|
|
7450
|
-
if (!options.force && isElectronAppInstalled()) {
|
|
7451
|
-
console.log(`Electron app v${version} is already installed at ${versionDir}`);
|
|
7452
|
-
console.log("Use --force to re-download.");
|
|
7453
|
-
return;
|
|
7454
|
-
}
|
|
7455
|
-
const binaryName = getBinaryName();
|
|
7456
|
-
const downloadUrl = buildElectronAppReleaseAssetUrl({
|
|
7457
|
-
version,
|
|
7458
|
-
assetFileName: binaryName
|
|
7459
|
-
});
|
|
7460
|
-
console.log(`Downloading Muggle Test Electron app v${version}...`);
|
|
7461
|
-
console.log(`URL: ${downloadUrl}`);
|
|
7462
|
-
try {
|
|
7463
|
-
if (existsSync(versionDir)) {
|
|
7464
|
-
rmSync(versionDir, { recursive: true, force: true });
|
|
7465
|
-
}
|
|
7466
|
-
mkdirSync(versionDir, { recursive: true });
|
|
7467
|
-
const tempFile = path2.join(versionDir, binaryName);
|
|
7468
|
-
await downloadWithRetry(downloadUrl, tempFile);
|
|
7469
|
-
console.log("Download complete, verifying checksum...");
|
|
7470
|
-
const checksums = getElectronAppChecksums();
|
|
7471
|
-
const expectedChecksum = getChecksumForPlatform(checksums);
|
|
7472
|
-
const checksumResult = await verifyFileChecksum(tempFile, expectedChecksum);
|
|
7473
|
-
if (!checksumResult.valid && expectedChecksum) {
|
|
7474
|
-
cleanupFailedInstall(versionDir);
|
|
7475
|
-
throw new Error(
|
|
7476
|
-
`Checksum verification failed!
|
|
7477
|
-
Expected: ${checksumResult.expected}
|
|
7478
|
-
Actual: ${checksumResult.actual}
|
|
7479
|
-
The downloaded file may be corrupted or tampered with.`
|
|
7480
|
-
);
|
|
7481
|
-
}
|
|
7482
|
-
if (expectedChecksum) {
|
|
7483
|
-
console.log("Checksum verified successfully.");
|
|
7484
|
-
} else {
|
|
7485
|
-
console.log("Warning: No checksum configured, skipping verification.");
|
|
7486
|
-
}
|
|
7487
|
-
console.log("Extracting...");
|
|
7488
|
-
if (binaryName.endsWith(".zip")) {
|
|
7489
|
-
await extractZip(tempFile, versionDir);
|
|
7490
|
-
} else if (binaryName.endsWith(".tar.gz")) {
|
|
7491
|
-
await extractTarGz(tempFile, versionDir);
|
|
7492
|
-
}
|
|
7493
|
-
const executablePath = getExpectedExecutablePath2(versionDir);
|
|
7494
|
-
if (!existsSync(executablePath)) {
|
|
7495
|
-
cleanupFailedInstall(versionDir);
|
|
7496
|
-
throw new Error(
|
|
7497
|
-
`Extraction failed: executable not found at expected path.
|
|
7498
|
-
Expected: ${executablePath}
|
|
7499
|
-
The archive may be corrupted or in an unexpected format.`
|
|
7500
|
-
);
|
|
7501
|
-
}
|
|
7502
|
-
const executableChecksum = await calculateFileChecksum(executablePath);
|
|
7503
|
-
const metadataPath = path2.join(versionDir, INSTALL_METADATA_FILE_NAME);
|
|
7504
|
-
writeInstallMetadata({
|
|
7505
|
-
metadataPath,
|
|
7506
|
-
version,
|
|
7507
|
-
binaryName,
|
|
7508
|
-
platformKey,
|
|
7509
|
-
executableChecksum,
|
|
7510
|
-
expectedArchiveChecksum: expectedChecksum
|
|
7511
|
-
});
|
|
7512
|
-
rmSync(tempFile, { force: true });
|
|
7513
|
-
console.log(`Electron app installed to ${versionDir}`);
|
|
7514
|
-
logger11.info("Setup complete", { version, path: versionDir });
|
|
7515
|
-
} catch (error) {
|
|
7516
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7517
|
-
console.error(`Failed to download Electron app: ${errorMessage}`);
|
|
7518
|
-
logger11.error("Setup failed", { error: errorMessage });
|
|
7519
|
-
process.exit(1);
|
|
7520
|
-
}
|
|
7521
|
-
}
|
|
7522
|
-
var logger12 = getLogger();
|
|
7523
|
-
var GITHUB_RELEASES_API = "https://api.github.com/repos/multiplex-ai/muggle-ai-works/releases";
|
|
7524
|
-
var INSTALL_METADATA_FILE_NAME2 = ".install-metadata.json";
|
|
7525
|
-
var VERSION_OVERRIDE_FILE2 = "electron-app-version-override.json";
|
|
7526
|
-
function getBinaryName2() {
|
|
7527
|
-
const os4 = platform();
|
|
7528
|
-
const arch3 = process.arch;
|
|
7529
|
-
switch (os4) {
|
|
7530
|
-
case "darwin":
|
|
7531
|
-
return arch3 === "arm64" ? "MuggleAI-darwin-arm64.zip" : "MuggleAI-darwin-x64.zip";
|
|
7532
|
-
case "win32":
|
|
7533
|
-
return "MuggleAI-win32-x64.zip";
|
|
7534
|
-
case "linux":
|
|
7535
|
-
return "MuggleAI-linux-x64.zip";
|
|
7536
|
-
default:
|
|
7537
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
7538
|
-
}
|
|
7539
|
-
}
|
|
7540
|
-
function extractVersionFromTag(tag) {
|
|
7541
|
-
const match = tag.match(/^(?:electron-app-)?v(\d+\.\d+\.\d+)$/);
|
|
7542
|
-
return match ? match[1] : null;
|
|
7543
|
-
}
|
|
7544
|
-
function getVersionOverridePath() {
|
|
7545
|
-
return path2.join(getDataDir2(), VERSION_OVERRIDE_FILE2);
|
|
7546
|
-
}
|
|
7547
|
-
function getEffectiveElectronAppVersion() {
|
|
7548
|
-
const overridePath = getVersionOverridePath();
|
|
7549
|
-
if (existsSync(overridePath)) {
|
|
7550
|
-
try {
|
|
7551
|
-
const content = JSON.parse(__require("fs").readFileSync(overridePath, "utf-8"));
|
|
7552
|
-
if (content.version) {
|
|
7553
|
-
return content.version;
|
|
7554
|
-
}
|
|
7555
|
-
} catch {
|
|
7556
|
-
}
|
|
7557
|
-
}
|
|
7558
|
-
return getElectronAppVersion();
|
|
7559
|
-
}
|
|
7560
|
-
function saveVersionOverride(version) {
|
|
7561
|
-
const overridePath = getVersionOverridePath();
|
|
7562
|
-
const dataDir = getDataDir2();
|
|
7563
|
-
if (!existsSync(dataDir)) {
|
|
7564
|
-
mkdirSync(dataDir, { recursive: true });
|
|
7565
|
-
}
|
|
7566
|
-
writeFileSync(overridePath, JSON.stringify({
|
|
7567
|
-
version,
|
|
7568
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7569
|
-
}, null, 2), "utf-8");
|
|
7570
|
-
}
|
|
7571
|
-
async function checkForUpdates() {
|
|
7572
|
-
const currentVersion = getEffectiveElectronAppVersion();
|
|
7573
|
-
try {
|
|
7574
|
-
const response = await fetch(GITHUB_RELEASES_API, {
|
|
7575
|
-
headers: {
|
|
7576
|
-
"Accept": "application/vnd.github.v3+json",
|
|
7577
|
-
"User-Agent": "muggle"
|
|
7578
|
-
}
|
|
7579
|
-
});
|
|
7580
|
-
if (!response.ok) {
|
|
7581
|
-
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
7582
|
-
}
|
|
7583
|
-
const releases = await response.json();
|
|
7584
|
-
for (const release2 of releases) {
|
|
7585
|
-
if (release2.prerelease || release2.draft) {
|
|
7586
|
-
continue;
|
|
7587
|
-
}
|
|
7588
|
-
const version = extractVersionFromTag(release2.tag_name);
|
|
7589
|
-
if (version) {
|
|
7590
|
-
const updateAvailable = compareVersions2(version, currentVersion) > 0;
|
|
7591
|
-
const binaryName = getBinaryName2();
|
|
7592
|
-
return {
|
|
7593
|
-
currentVersion,
|
|
7594
|
-
latestVersion: version,
|
|
7595
|
-
updateAvailable,
|
|
7596
|
-
downloadUrl: buildElectronAppReleaseAssetUrl({
|
|
7597
|
-
version,
|
|
7598
|
-
assetFileName: binaryName
|
|
7599
|
-
})
|
|
7600
|
-
};
|
|
7601
|
-
}
|
|
7602
|
-
}
|
|
7603
|
-
return {
|
|
7604
|
-
currentVersion,
|
|
7605
|
-
latestVersion: currentVersion,
|
|
7606
|
-
updateAvailable: false
|
|
7607
|
-
};
|
|
7608
|
-
} catch (error) {
|
|
7609
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7610
|
-
logger12.warn("Failed to check for updates", { error: errorMessage });
|
|
7611
|
-
throw new Error(`Failed to check for updates: ${errorMessage}`, { cause: error });
|
|
7612
|
-
}
|
|
7613
|
-
}
|
|
7614
|
-
function compareVersions2(a, b) {
|
|
7615
|
-
const partsA = a.split(".").map(Number);
|
|
7616
|
-
const partsB = b.split(".").map(Number);
|
|
7617
|
-
for (let i = 0; i < 3; i++) {
|
|
7618
|
-
const partA = partsA[i] || 0;
|
|
7619
|
-
const partB = partsB[i] || 0;
|
|
7620
|
-
if (partA > partB) {
|
|
7621
|
-
return 1;
|
|
7622
|
-
}
|
|
7623
|
-
if (partA < partB) {
|
|
7624
|
-
return -1;
|
|
7625
|
-
}
|
|
7626
|
-
}
|
|
7627
|
-
return 0;
|
|
7628
|
-
}
|
|
7629
|
-
function getExpectedExecutablePath3(versionDir) {
|
|
7630
|
-
const os4 = platform();
|
|
7631
|
-
switch (os4) {
|
|
7632
|
-
case "darwin":
|
|
7633
|
-
return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
7634
|
-
case "win32":
|
|
7635
|
-
return path2.join(versionDir, "MuggleAI.exe");
|
|
7636
|
-
case "linux":
|
|
7637
|
-
return path2.join(versionDir, "MuggleAI");
|
|
7638
|
-
default:
|
|
7639
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
7640
|
-
}
|
|
7641
|
-
}
|
|
7642
|
-
function writeInstallMetadata2(params) {
|
|
7643
|
-
const metadata = {
|
|
7644
|
-
version: params.version,
|
|
7645
|
-
binaryName: params.binaryName,
|
|
7646
|
-
platformKey: params.platformKey,
|
|
7647
|
-
executableChecksum: params.executableChecksum,
|
|
7648
|
-
expectedArchiveChecksum: params.expectedArchiveChecksum,
|
|
7649
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7650
|
-
};
|
|
7651
|
-
writeFileSync(params.metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
7652
|
-
`, "utf-8");
|
|
7653
|
-
}
|
|
7654
|
-
async function extractZip2(zipPath, destDir) {
|
|
7655
|
-
return new Promise((resolve4, reject) => {
|
|
7656
|
-
if (platform() === "win32") {
|
|
7657
|
-
execFile(
|
|
7658
|
-
"powershell",
|
|
7659
|
-
["-command", `Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force`],
|
|
7660
|
-
(error) => {
|
|
7661
|
-
if (error) {
|
|
7662
|
-
reject(error);
|
|
7663
|
-
} else {
|
|
7664
|
-
resolve4();
|
|
7665
|
-
}
|
|
7666
|
-
}
|
|
7667
|
-
);
|
|
7668
|
-
} else {
|
|
7669
|
-
execFile("unzip", ["-o", zipPath, "-d", destDir], (error) => {
|
|
7670
|
-
if (error) {
|
|
7671
|
-
reject(error);
|
|
7672
|
-
} else {
|
|
7673
|
-
resolve4();
|
|
7674
|
-
}
|
|
7675
|
-
});
|
|
7676
|
-
}
|
|
7677
|
-
});
|
|
7678
|
-
}
|
|
7679
|
-
async function extractTarGz2(tarPath, destDir) {
|
|
7680
|
-
return new Promise((resolve4, reject) => {
|
|
7681
|
-
execFile("tar", ["-xzf", tarPath, "-C", destDir], (error) => {
|
|
7682
|
-
if (error) {
|
|
7683
|
-
reject(error);
|
|
7684
|
-
} else {
|
|
7685
|
-
resolve4();
|
|
7686
|
-
}
|
|
7687
|
-
});
|
|
7688
|
-
});
|
|
7689
|
-
}
|
|
7690
|
-
async function fetchChecksumFromRelease(version) {
|
|
7691
|
-
const checksumUrl = buildElectronAppChecksumsUrl(version);
|
|
7692
|
-
try {
|
|
7693
|
-
const response = await fetch(checksumUrl);
|
|
7694
|
-
if (!response.ok) {
|
|
7695
|
-
logger12.warn("Checksums file not found in release", { version });
|
|
7696
|
-
return "";
|
|
7697
|
-
}
|
|
7698
|
-
const text = await response.text();
|
|
7699
|
-
const binaryName = getBinaryName2();
|
|
7700
|
-
const platformKey = getPlatformKey();
|
|
7701
|
-
const lines = text.split("\n");
|
|
7702
|
-
for (const line of lines) {
|
|
7703
|
-
const trimmed = line.trim();
|
|
7704
|
-
if (!trimmed) {
|
|
7705
|
-
continue;
|
|
7706
|
-
}
|
|
7707
|
-
const match = trimmed.match(/^([a-fA-F0-9]{64})\s+(.+)$/);
|
|
7708
|
-
if (match) {
|
|
7709
|
-
const checksum = match[1];
|
|
7710
|
-
const filename = match[2];
|
|
7711
|
-
if (filename === binaryName || filename.includes(platformKey)) {
|
|
7712
|
-
logger12.info("Found checksum in release", {
|
|
7713
|
-
version,
|
|
7714
|
-
platform: platformKey,
|
|
7715
|
-
checksum: checksum.substring(0, 16) + "..."
|
|
7716
|
-
});
|
|
7717
|
-
return checksum;
|
|
7718
|
-
}
|
|
7719
|
-
}
|
|
7720
|
-
}
|
|
7721
|
-
logger12.warn("Platform checksum not found in checksums.txt", {
|
|
7722
|
-
version,
|
|
7723
|
-
platform: platformKey
|
|
7724
|
-
});
|
|
7725
|
-
return "";
|
|
7726
|
-
} catch (error) {
|
|
7727
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7728
|
-
logger12.warn("Failed to fetch checksums from release", {
|
|
7729
|
-
version,
|
|
7730
|
-
error: errorMessage
|
|
7731
|
-
});
|
|
7732
|
-
return "";
|
|
7733
|
-
}
|
|
7734
|
-
}
|
|
7735
|
-
async function downloadAndInstall(version, downloadUrl, checksum) {
|
|
7736
|
-
const versionDir = getElectronAppDir(version);
|
|
7737
|
-
const binaryName = getBinaryName2();
|
|
7738
|
-
const platformKey = getPlatformKey();
|
|
7739
|
-
console.log(`Downloading Muggle Test Electron app v${version}...`);
|
|
7740
|
-
console.log(`URL: ${downloadUrl}`);
|
|
7741
|
-
if (existsSync(versionDir)) {
|
|
7742
|
-
rmSync(versionDir, { recursive: true, force: true });
|
|
7743
|
-
}
|
|
7744
|
-
mkdirSync(versionDir, { recursive: true });
|
|
7745
|
-
const response = await fetch(downloadUrl);
|
|
7746
|
-
if (!response.ok) {
|
|
7747
|
-
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
7748
|
-
}
|
|
7749
|
-
const tempFile = path2.join(versionDir, binaryName);
|
|
7750
|
-
const fileStream = createWriteStream(tempFile);
|
|
7751
|
-
if (!response.body) {
|
|
7752
|
-
throw new Error("No response body");
|
|
7753
|
-
}
|
|
7754
|
-
await pipeline(response.body, fileStream);
|
|
7755
|
-
console.log("Download complete, verifying checksum...");
|
|
7756
|
-
let expectedChecksum = checksum;
|
|
7757
|
-
if (!expectedChecksum) {
|
|
7758
|
-
expectedChecksum = await fetchChecksumFromRelease(version);
|
|
7759
|
-
}
|
|
7760
|
-
const checksumResult = await verifyFileChecksum(tempFile, expectedChecksum || "");
|
|
7761
|
-
if (!checksumResult.valid && expectedChecksum) {
|
|
7762
|
-
rmSync(versionDir, { recursive: true, force: true });
|
|
7763
|
-
throw new Error(
|
|
7764
|
-
`Checksum verification failed!
|
|
7765
|
-
Expected: ${checksumResult.expected}
|
|
7766
|
-
Actual: ${checksumResult.actual}
|
|
7767
|
-
The downloaded file may be corrupted or tampered with.`
|
|
7768
|
-
);
|
|
7769
|
-
}
|
|
7770
|
-
if (expectedChecksum) {
|
|
7771
|
-
console.log("Checksum verified successfully.");
|
|
7772
|
-
} else {
|
|
7773
|
-
console.log("Warning: No checksum available, skipping verification.");
|
|
7774
|
-
}
|
|
7775
|
-
console.log("Extracting...");
|
|
7776
|
-
if (binaryName.endsWith(".zip")) {
|
|
7777
|
-
await extractZip2(tempFile, versionDir);
|
|
7778
|
-
} else if (binaryName.endsWith(".tar.gz")) {
|
|
7779
|
-
await extractTarGz2(tempFile, versionDir);
|
|
7780
|
-
}
|
|
7781
|
-
const executablePath = getExpectedExecutablePath3(versionDir);
|
|
7782
|
-
if (!existsSync(executablePath)) {
|
|
7783
|
-
rmSync(versionDir, { recursive: true, force: true });
|
|
7784
|
-
throw new Error(
|
|
7785
|
-
`Extraction failed: executable not found at expected path.
|
|
7786
|
-
Expected: ${executablePath}
|
|
7787
|
-
The archive may be corrupted or in an unexpected format.`
|
|
7788
|
-
);
|
|
7789
|
-
}
|
|
7790
|
-
const executableChecksum = await calculateFileChecksum(executablePath);
|
|
7791
|
-
const metadataPath = path2.join(versionDir, INSTALL_METADATA_FILE_NAME2);
|
|
7792
|
-
writeInstallMetadata2({
|
|
7793
|
-
metadataPath,
|
|
7794
|
-
version,
|
|
7795
|
-
binaryName,
|
|
7796
|
-
platformKey,
|
|
7797
|
-
executableChecksum,
|
|
7798
|
-
expectedArchiveChecksum: expectedChecksum || ""
|
|
7799
|
-
});
|
|
7800
|
-
rmSync(tempFile, { force: true });
|
|
7801
|
-
saveVersionOverride(version);
|
|
7802
|
-
console.log(`Electron app v${version} installed to ${versionDir}`);
|
|
7803
|
-
logger12.info("Upgrade complete", { version, path: versionDir });
|
|
7804
|
-
}
|
|
7805
|
-
async function upgradeCommand(options) {
|
|
7806
|
-
try {
|
|
7807
|
-
if (options.version) {
|
|
7808
|
-
const binaryName = getBinaryName2();
|
|
7809
|
-
const downloadUrl = buildElectronAppReleaseAssetUrl({
|
|
7810
|
-
version: options.version,
|
|
7811
|
-
assetFileName: binaryName
|
|
7812
|
-
});
|
|
7813
|
-
await downloadAndInstall(options.version, downloadUrl);
|
|
7814
|
-
const cleanupResult2 = cleanupOldVersions({ all: false });
|
|
7815
|
-
if (cleanupResult2.removed.length > 0) {
|
|
7816
|
-
console.log(
|
|
7817
|
-
`
|
|
7818
|
-
Cleaned up ${cleanupResult2.removed.length} old version(s), freed ${formatBytes(cleanupResult2.freedBytes)}`
|
|
7819
|
-
);
|
|
7820
|
-
}
|
|
7821
|
-
return;
|
|
7822
|
-
}
|
|
7823
|
-
console.log("Checking for updates...");
|
|
7824
|
-
const result = await checkForUpdates();
|
|
7825
|
-
console.log(`Current version: ${result.currentVersion}`);
|
|
7826
|
-
console.log(`Latest version: ${result.latestVersion}`);
|
|
7827
|
-
if (options.check) {
|
|
7828
|
-
if (result.updateAvailable) {
|
|
7829
|
-
console.log("\nUpdate available! Run 'muggle upgrade' to install.");
|
|
7830
|
-
} else {
|
|
7831
|
-
console.log("\nYou are on the latest version.");
|
|
7832
|
-
}
|
|
7833
|
-
return;
|
|
7834
|
-
}
|
|
7835
|
-
if (!result.updateAvailable && !options.force) {
|
|
7836
|
-
console.log("\nYou are already on the latest version.");
|
|
7837
|
-
console.log("Use --force to re-download the current version.");
|
|
7838
|
-
return;
|
|
7839
|
-
}
|
|
7840
|
-
if (!result.downloadUrl) {
|
|
7841
|
-
throw new Error("No download URL available");
|
|
7842
|
-
}
|
|
7843
|
-
await downloadAndInstall(result.latestVersion, result.downloadUrl);
|
|
7844
|
-
const cleanupResult = cleanupOldVersions({ all: false });
|
|
7845
|
-
if (cleanupResult.removed.length > 0) {
|
|
7846
|
-
console.log(
|
|
7847
|
-
`
|
|
7848
|
-
Cleaned up ${cleanupResult.removed.length} old version(s), freed ${formatBytes(cleanupResult.freedBytes)}`
|
|
7849
|
-
);
|
|
7850
|
-
}
|
|
7851
|
-
} catch (error) {
|
|
7852
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7853
|
-
console.error(`Upgrade failed: ${errorMessage}`);
|
|
7854
|
-
logger12.error("Upgrade failed", { error: errorMessage });
|
|
7855
|
-
process.exit(1);
|
|
7856
|
-
}
|
|
7857
|
-
}
|
|
7858
|
-
|
|
7859
|
-
// packages/commands/src/cli/run-cli.ts
|
|
7860
|
-
var __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
7861
|
-
function getPackageRoot2() {
|
|
7862
|
-
if (__dirname$1.endsWith("dist")) {
|
|
7863
|
-
return resolve(__dirname$1, "..");
|
|
7864
|
-
}
|
|
7865
|
-
return resolve(__dirname$1, "..", "..", "..", "..");
|
|
7866
|
-
}
|
|
7867
|
-
var packageVersion = JSON.parse(
|
|
7868
|
-
readFileSync(resolve(getPackageRoot2(), "package.json"), "utf-8")
|
|
7869
|
-
).version;
|
|
7870
|
-
var logger13 = getLogger();
|
|
7871
|
-
function createProgram() {
|
|
7872
|
-
const program = new Command();
|
|
7873
|
-
program.name("muggle").description("Unified MCP server for Muggle AI \u2014 cloud E2E and local E2E testing").version(packageVersion);
|
|
7874
|
-
program.command("serve").description("Start the MCP server").option("--e2e", "Only enable cloud E2E tools (remote URLs; muggle-remote-* prefix)").option("--local", "Only enable local E2E tools (localhost; muggle-local-* prefix)").option("--stdio", "Use stdio transport (default)").action(serveCommand);
|
|
7875
|
-
program.command("setup").description("Download/update the Electron app for local testing").option("--force", "Force re-download even if already installed").action(setupCommand);
|
|
7876
|
-
program.command("upgrade").description("Check for and install the latest electron-app version").option("--force", "Force re-download even if already on latest").option("--check", "Check for updates only, don't download").option("--version <version>", "Download a specific version (e.g., 1.0.2)").action(upgradeCommand);
|
|
7877
|
-
program.command("versions").description("List installed electron-app versions").action(versionsCommand);
|
|
7878
|
-
program.command("cleanup").description("Remove old electron-app versions and obsolete skills").option("--all", "Remove all old versions (default: keep one previous)").option("--dry-run", "Show what would be deleted without deleting").option("--skills", "Also clean up obsolete skills from ~/.cursor/skills").action(cleanupCommand);
|
|
7879
|
-
program.command("doctor").description("Diagnose installation and configuration issues").action(doctorCommand);
|
|
7880
|
-
program.command("login").description("Authenticate with Muggle AI (uses device code flow)").option("--key-name <name>", "Name for the API key").option("--key-expiry <expiry>", "API key expiry: 30d, 90d, 1y, never", "90d").action(loginCommand);
|
|
7881
|
-
program.command("logout").description("Clear stored credentials").action(logoutCommand);
|
|
7882
|
-
program.command("status").description("Show authentication status").action(statusCommand);
|
|
7883
|
-
program.command("build-pr-section").description("Render a muggle-do PR body evidence block from an e2e report on stdin").option("--max-body-bytes <n>", "Max UTF-8 byte budget for the PR body (default 60000)").action(buildPrSectionCommand);
|
|
7884
|
-
program.action(() => {
|
|
7885
|
-
helpCommand();
|
|
7886
|
-
});
|
|
7887
|
-
program.on("command:*", () => {
|
|
7888
|
-
helpCommand();
|
|
7889
|
-
process.exit(1);
|
|
7890
|
-
});
|
|
7891
|
-
return program;
|
|
7892
|
-
}
|
|
7893
|
-
function handleHelpCommand() {
|
|
7894
|
-
const args = process.argv.slice(2);
|
|
7895
|
-
if (args.length === 1 && args[0] === "help") {
|
|
7896
|
-
helpCommand();
|
|
7897
|
-
return true;
|
|
7898
|
-
}
|
|
7899
|
-
return false;
|
|
7900
|
-
}
|
|
7901
|
-
async function runCli() {
|
|
7902
|
-
try {
|
|
7903
|
-
if (handleHelpCommand()) {
|
|
7904
|
-
return;
|
|
7905
|
-
}
|
|
7906
|
-
const program = createProgram();
|
|
7907
|
-
await program.parseAsync(process.argv);
|
|
7908
|
-
} catch (error) {
|
|
7909
|
-
logger13.error("CLI error", {
|
|
7910
|
-
error: error instanceof Error ? error.message : String(error)
|
|
7911
|
-
});
|
|
7912
|
-
process.exit(1);
|
|
7913
|
-
}
|
|
7914
|
-
}
|
|
7915
|
-
|
|
7916
|
-
// packages/commands/src/index.ts
|
|
7917
|
-
var src_exports2 = {};
|
|
7918
|
-
__export(src_exports2, {
|
|
7919
|
-
cleanupCommand: () => cleanupCommand,
|
|
7920
|
-
doctorCommand: () => doctorCommand,
|
|
7921
|
-
helpCommand: () => helpCommand,
|
|
7922
|
-
loginCommand: () => loginCommand,
|
|
7923
|
-
logoutCommand: () => logoutCommand,
|
|
7924
|
-
registerCoreCommands: () => registerCoreCommands,
|
|
7925
|
-
runCli: () => runCli,
|
|
7926
|
-
serveCommand: () => serveCommand,
|
|
7927
|
-
setupCommand: () => setupCommand,
|
|
7928
|
-
statusCommand: () => statusCommand,
|
|
7929
|
-
upgradeCommand: () => upgradeCommand,
|
|
7930
|
-
versionsCommand: () => versionsCommand
|
|
7931
|
-
});
|
|
7932
|
-
|
|
7933
|
-
// packages/commands/src/registry/register-core-commands.ts
|
|
7934
|
-
function registerCoreCommands(commandRegistrationContext) {
|
|
7935
|
-
}
|
|
7936
|
-
|
|
7937
|
-
export { createChildLogger, createUnifiedMcpServer, e2e_exports2 as e2e_exports, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports2 as local_exports, mcp_exports, runCli, server_exports, src_exports, src_exports2 };
|
|
5911
|
+
export { __export, __require, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, calculateFileChecksum, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports2 as e2e_exports, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir2 as getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getPlatformKey, getQaTools, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, loadApiKeyData, loadCredentials, local_exports2 as local_exports, mcp_exports, openBrowserUrl, performLogin, performLogout, pollDeviceCode, resetConfig, resetLogger, saveApiKey, saveApiKeyData, saveCredentials, src_exports, startDeviceCodeFlow, toolRequiresAuth, verifyFileChecksum };
|