@muggleai/works 4.5.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-MNCBJEPQ.js → chunk-HDEZDEM6.js} +756 -2815
- 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/e2e-acceptance.md +6 -3
- 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 +111 -79
- package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +18 -15
- package/dist/plugin/skills/muggle-test-import/SKILL.md +5 -2
- package/dist/plugin/skills/muggle-test-regenerate-missing/SKILL.md +7 -2
- 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/e2e-acceptance.md +6 -3
- 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 +111 -79
- package/plugin/skills/muggle-test-feature-local/SKILL.md +18 -15
- package/plugin/skills/muggle-test-import/SKILL.md +5 -2
- package/plugin/skills/muggle-test-regenerate-missing/SKILL.md +7 -2
|
@@ -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
|
|
@@ -723,12 +866,13 @@ var AuthService = class {
|
|
|
723
866
|
}
|
|
724
867
|
/**
|
|
725
868
|
* Start the device code flow.
|
|
869
|
+
* @param options.forceNewSession - Clear existing Auth0 browser session before login to allow account switching.
|
|
726
870
|
*/
|
|
727
|
-
async startDeviceCodeFlow() {
|
|
728
|
-
const
|
|
871
|
+
async startDeviceCodeFlow(options) {
|
|
872
|
+
const logger6 = getLogger();
|
|
729
873
|
const config = getConfig();
|
|
730
874
|
const { domain, clientId, audience, scopes } = config.localQa.auth0;
|
|
731
|
-
|
|
875
|
+
logger6.info("Starting device code flow");
|
|
732
876
|
const url = `https://${domain}/oauth/device/code`;
|
|
733
877
|
const body = new URLSearchParams({
|
|
734
878
|
client_id: clientId,
|
|
@@ -744,14 +888,14 @@ var AuthService = class {
|
|
|
744
888
|
});
|
|
745
889
|
if (!response.ok) {
|
|
746
890
|
const errorText = await response.text();
|
|
747
|
-
|
|
891
|
+
logger6.error("Device code request failed", {
|
|
748
892
|
status: response.status,
|
|
749
893
|
error: errorText
|
|
750
894
|
});
|
|
751
895
|
throw new Error(`Failed to start device code flow: ${response.status} ${errorText}`);
|
|
752
896
|
}
|
|
753
897
|
const data = await response.json();
|
|
754
|
-
|
|
898
|
+
logger6.info("Device code flow started", {
|
|
755
899
|
userCode: data.user_code,
|
|
756
900
|
verificationUri: data.verification_uri,
|
|
757
901
|
expiresIn: data.expires_in
|
|
@@ -761,15 +905,25 @@ var AuthService = class {
|
|
|
761
905
|
userCode: data.user_code,
|
|
762
906
|
expiresAt: new Date(Date.now() + data.expires_in * 1e3).toISOString()
|
|
763
907
|
});
|
|
908
|
+
let browserUrl = data.verification_uri_complete;
|
|
909
|
+
if (options?.forceNewSession) {
|
|
910
|
+
const logoutUrl = new URL(`https://${domain}/v2/logout`);
|
|
911
|
+
logoutUrl.searchParams.set("client_id", clientId);
|
|
912
|
+
logoutUrl.searchParams.set("returnTo", data.verification_uri_complete);
|
|
913
|
+
browserUrl = logoutUrl.toString();
|
|
914
|
+
logger6.info("Force new session: opening logout-redirect URL", {
|
|
915
|
+
logoutUrl: browserUrl
|
|
916
|
+
});
|
|
917
|
+
}
|
|
764
918
|
const browserOpenResult = await openBrowserUrl({
|
|
765
|
-
url:
|
|
919
|
+
url: browserUrl
|
|
766
920
|
});
|
|
767
921
|
if (browserOpenResult.opened) {
|
|
768
|
-
|
|
922
|
+
logger6.info("Browser opened for device code login");
|
|
769
923
|
} else {
|
|
770
|
-
|
|
924
|
+
logger6.warn("Failed to open browser for device code login", {
|
|
771
925
|
error: browserOpenResult.error,
|
|
772
|
-
|
|
926
|
+
url: browserUrl
|
|
773
927
|
});
|
|
774
928
|
}
|
|
775
929
|
return {
|
|
@@ -787,39 +941,39 @@ var AuthService = class {
|
|
|
787
941
|
* Store a pending device code for later retrieval.
|
|
788
942
|
*/
|
|
789
943
|
storePendingDeviceCode(params) {
|
|
790
|
-
const
|
|
944
|
+
const logger6 = getLogger();
|
|
791
945
|
const dir = path2.dirname(this.pendingDeviceCodePath);
|
|
792
|
-
if (!
|
|
793
|
-
|
|
946
|
+
if (!fs5.existsSync(dir)) {
|
|
947
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
794
948
|
}
|
|
795
|
-
|
|
949
|
+
fs5.writeFileSync(this.pendingDeviceCodePath, JSON.stringify(params, null, 2), {
|
|
796
950
|
encoding: "utf-8",
|
|
797
951
|
mode: 384
|
|
798
952
|
});
|
|
799
|
-
|
|
953
|
+
logger6.debug("Pending device code stored", { userCode: params.userCode });
|
|
800
954
|
}
|
|
801
955
|
/**
|
|
802
956
|
* Get the pending device code if one exists and is not expired.
|
|
803
957
|
*/
|
|
804
958
|
getPendingDeviceCode() {
|
|
805
|
-
const
|
|
806
|
-
if (!
|
|
807
|
-
|
|
959
|
+
const logger6 = getLogger();
|
|
960
|
+
if (!fs5.existsSync(this.pendingDeviceCodePath)) {
|
|
961
|
+
logger6.debug("No pending device code found");
|
|
808
962
|
return null;
|
|
809
963
|
}
|
|
810
964
|
try {
|
|
811
|
-
const content =
|
|
965
|
+
const content = fs5.readFileSync(this.pendingDeviceCodePath, "utf-8");
|
|
812
966
|
const data = JSON.parse(content);
|
|
813
967
|
const now = /* @__PURE__ */ new Date();
|
|
814
968
|
const expiresAt = new Date(data.expiresAt);
|
|
815
969
|
if (now >= expiresAt) {
|
|
816
|
-
|
|
970
|
+
logger6.debug("Pending device code expired");
|
|
817
971
|
this.clearPendingDeviceCode();
|
|
818
972
|
return null;
|
|
819
973
|
}
|
|
820
974
|
return data.deviceCode;
|
|
821
975
|
} catch (error) {
|
|
822
|
-
|
|
976
|
+
logger6.warn("Failed to read pending device code", {
|
|
823
977
|
error: error instanceof Error ? error.message : String(error)
|
|
824
978
|
});
|
|
825
979
|
return null;
|
|
@@ -829,13 +983,13 @@ var AuthService = class {
|
|
|
829
983
|
* Clear the pending device code file.
|
|
830
984
|
*/
|
|
831
985
|
clearPendingDeviceCode() {
|
|
832
|
-
const
|
|
833
|
-
if (
|
|
986
|
+
const logger6 = getLogger();
|
|
987
|
+
if (fs5.existsSync(this.pendingDeviceCodePath)) {
|
|
834
988
|
try {
|
|
835
|
-
|
|
836
|
-
|
|
989
|
+
fs5.unlinkSync(this.pendingDeviceCodePath);
|
|
990
|
+
logger6.debug("Pending device code cleared");
|
|
837
991
|
} catch (error) {
|
|
838
|
-
|
|
992
|
+
logger6.warn("Failed to clear pending device code", {
|
|
839
993
|
error: error instanceof Error ? error.message : String(error)
|
|
840
994
|
});
|
|
841
995
|
}
|
|
@@ -845,10 +999,10 @@ var AuthService = class {
|
|
|
845
999
|
* Poll for device code authorization completion.
|
|
846
1000
|
*/
|
|
847
1001
|
async pollDeviceCode(deviceCode) {
|
|
848
|
-
const
|
|
1002
|
+
const logger6 = getLogger();
|
|
849
1003
|
const config = getConfig();
|
|
850
1004
|
const { domain, clientId } = config.localQa.auth0;
|
|
851
|
-
|
|
1005
|
+
logger6.debug("Polling for device code authorization");
|
|
852
1006
|
const url = `https://${domain}/oauth/token`;
|
|
853
1007
|
const body = new URLSearchParams({
|
|
854
1008
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
@@ -878,7 +1032,7 @@ var AuthService = class {
|
|
|
878
1032
|
userId: userInfo.sub
|
|
879
1033
|
});
|
|
880
1034
|
this.clearPendingDeviceCode();
|
|
881
|
-
|
|
1035
|
+
logger6.info("Device code authorization complete", { email: userInfo.email });
|
|
882
1036
|
return {
|
|
883
1037
|
status: "complete" /* Complete */,
|
|
884
1038
|
message: "Authentication successful!",
|
|
@@ -887,35 +1041,35 @@ var AuthService = class {
|
|
|
887
1041
|
}
|
|
888
1042
|
const errorData = await response.json();
|
|
889
1043
|
if (errorData.error === "authorization_pending") {
|
|
890
|
-
|
|
1044
|
+
logger6.debug("Authorization pending");
|
|
891
1045
|
return {
|
|
892
1046
|
status: "pending" /* Pending */,
|
|
893
1047
|
message: "Waiting for user to complete authorization..."
|
|
894
1048
|
};
|
|
895
1049
|
}
|
|
896
1050
|
if (errorData.error === "slow_down") {
|
|
897
|
-
|
|
1051
|
+
logger6.debug("Polling too fast");
|
|
898
1052
|
return {
|
|
899
1053
|
status: "pending" /* Pending */,
|
|
900
1054
|
message: "Polling too fast, slowing down..."
|
|
901
1055
|
};
|
|
902
1056
|
}
|
|
903
1057
|
if (errorData.error === "expired_token") {
|
|
904
|
-
|
|
1058
|
+
logger6.warn("Device code expired");
|
|
905
1059
|
return {
|
|
906
1060
|
status: "expired" /* Expired */,
|
|
907
1061
|
message: "The authorization code has expired. Please start again."
|
|
908
1062
|
};
|
|
909
1063
|
}
|
|
910
1064
|
if (errorData.error === "access_denied") {
|
|
911
|
-
|
|
1065
|
+
logger6.warn("Access denied");
|
|
912
1066
|
return {
|
|
913
1067
|
status: "error" /* Error */,
|
|
914
1068
|
message: "Access was denied by the user.",
|
|
915
1069
|
error: errorData.error_description ?? errorData.error
|
|
916
1070
|
};
|
|
917
1071
|
}
|
|
918
|
-
|
|
1072
|
+
logger6.error("Unexpected error during polling", { error: errorData });
|
|
919
1073
|
return {
|
|
920
1074
|
status: "error" /* Error */,
|
|
921
1075
|
message: errorData.error_description ?? errorData.error,
|
|
@@ -923,7 +1077,7 @@ var AuthService = class {
|
|
|
923
1077
|
};
|
|
924
1078
|
} catch (error) {
|
|
925
1079
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
926
|
-
|
|
1080
|
+
logger6.error("Poll request failed", { error: errorMessage });
|
|
927
1081
|
return {
|
|
928
1082
|
status: "error" /* Error */,
|
|
929
1083
|
message: `Poll request failed: ${errorMessage}`,
|
|
@@ -935,11 +1089,11 @@ var AuthService = class {
|
|
|
935
1089
|
* Poll for device code authorization until completion or timeout.
|
|
936
1090
|
*/
|
|
937
1091
|
async waitForDeviceCodeAuthorization(params) {
|
|
938
|
-
const
|
|
1092
|
+
const logger6 = getLogger();
|
|
939
1093
|
const timeoutMs = params.timeoutMs ?? DEFAULT_LOGIN_WAIT_TIMEOUT_MS;
|
|
940
1094
|
const pollIntervalMs = Math.max(params.intervalSeconds, 1) * 1e3;
|
|
941
1095
|
const startedAt = Date.now();
|
|
942
|
-
|
|
1096
|
+
logger6.info("Waiting for device code authorization", {
|
|
943
1097
|
timeoutMs,
|
|
944
1098
|
pollIntervalMs
|
|
945
1099
|
});
|
|
@@ -963,7 +1117,7 @@ var AuthService = class {
|
|
|
963
1117
|
* Get user info from Auth0.
|
|
964
1118
|
*/
|
|
965
1119
|
async getUserInfo(accessToken) {
|
|
966
|
-
const
|
|
1120
|
+
const logger6 = getLogger();
|
|
967
1121
|
const config = getConfig();
|
|
968
1122
|
const { domain } = config.localQa.auth0;
|
|
969
1123
|
const url = `https://${domain}/userinfo`;
|
|
@@ -975,13 +1129,13 @@ var AuthService = class {
|
|
|
975
1129
|
}
|
|
976
1130
|
});
|
|
977
1131
|
if (!response.ok) {
|
|
978
|
-
|
|
1132
|
+
logger6.warn("Failed to get user info", { status: response.status });
|
|
979
1133
|
return {};
|
|
980
1134
|
}
|
|
981
1135
|
const data = await response.json();
|
|
982
1136
|
return data;
|
|
983
1137
|
} catch (error) {
|
|
984
|
-
|
|
1138
|
+
logger6.warn("User info request failed", {
|
|
985
1139
|
error: error instanceof Error ? error.message : String(error)
|
|
986
1140
|
});
|
|
987
1141
|
return {};
|
|
@@ -992,7 +1146,7 @@ var AuthService = class {
|
|
|
992
1146
|
*/
|
|
993
1147
|
async storeAuth(params) {
|
|
994
1148
|
const { tokenResponse, email, userId } = params;
|
|
995
|
-
const
|
|
1149
|
+
const logger6 = getLogger();
|
|
996
1150
|
const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1e3).toISOString();
|
|
997
1151
|
const storedAuth = {
|
|
998
1152
|
accessToken: tokenResponse.accessToken,
|
|
@@ -1002,28 +1156,28 @@ var AuthService = class {
|
|
|
1002
1156
|
userId
|
|
1003
1157
|
};
|
|
1004
1158
|
const dir = path2.dirname(this.oauthSessionFilePath);
|
|
1005
|
-
if (!
|
|
1006
|
-
|
|
1159
|
+
if (!fs5.existsSync(dir)) {
|
|
1160
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1007
1161
|
}
|
|
1008
|
-
|
|
1162
|
+
fs5.writeFileSync(this.oauthSessionFilePath, JSON.stringify(storedAuth, null, 2), {
|
|
1009
1163
|
encoding: "utf-8",
|
|
1010
1164
|
mode: 384
|
|
1011
1165
|
});
|
|
1012
|
-
|
|
1166
|
+
logger6.info("Auth stored successfully", { email, expiresAt });
|
|
1013
1167
|
}
|
|
1014
1168
|
/**
|
|
1015
1169
|
* Load stored authentication.
|
|
1016
1170
|
*/
|
|
1017
1171
|
loadStoredAuth() {
|
|
1018
|
-
const
|
|
1019
|
-
if (!
|
|
1172
|
+
const logger6 = getLogger();
|
|
1173
|
+
if (!fs5.existsSync(this.oauthSessionFilePath)) {
|
|
1020
1174
|
return null;
|
|
1021
1175
|
}
|
|
1022
1176
|
try {
|
|
1023
|
-
const content =
|
|
1177
|
+
const content = fs5.readFileSync(this.oauthSessionFilePath, "utf-8");
|
|
1024
1178
|
return JSON.parse(content);
|
|
1025
1179
|
} catch (error) {
|
|
1026
|
-
|
|
1180
|
+
logger6.error("Failed to load stored auth", {
|
|
1027
1181
|
error: error instanceof Error ? error.message : String(error)
|
|
1028
1182
|
});
|
|
1029
1183
|
return null;
|
|
@@ -1064,15 +1218,15 @@ var AuthService = class {
|
|
|
1064
1218
|
* @returns New access token or null if refresh failed.
|
|
1065
1219
|
*/
|
|
1066
1220
|
async refreshAccessToken() {
|
|
1067
|
-
const
|
|
1221
|
+
const logger6 = getLogger();
|
|
1068
1222
|
const storedAuth = this.loadStoredAuth();
|
|
1069
1223
|
if (!storedAuth?.refreshToken) {
|
|
1070
|
-
|
|
1224
|
+
logger6.debug("No refresh token available");
|
|
1071
1225
|
return null;
|
|
1072
1226
|
}
|
|
1073
1227
|
const config = getConfig();
|
|
1074
1228
|
const { domain, clientId } = config.localQa.auth0;
|
|
1075
|
-
|
|
1229
|
+
logger6.info("Refreshing access token");
|
|
1076
1230
|
const url = `https://${domain}/oauth/token`;
|
|
1077
1231
|
const body = new URLSearchParams({
|
|
1078
1232
|
grant_type: "refresh_token",
|
|
@@ -1089,7 +1243,7 @@ var AuthService = class {
|
|
|
1089
1243
|
});
|
|
1090
1244
|
if (!response.ok) {
|
|
1091
1245
|
const errorText = await response.text();
|
|
1092
|
-
|
|
1246
|
+
logger6.error("Token refresh failed", {
|
|
1093
1247
|
status: response.status,
|
|
1094
1248
|
error: errorText
|
|
1095
1249
|
});
|
|
@@ -1105,17 +1259,17 @@ var AuthService = class {
|
|
|
1105
1259
|
userId: storedAuth.userId
|
|
1106
1260
|
};
|
|
1107
1261
|
const dir = path2.dirname(this.oauthSessionFilePath);
|
|
1108
|
-
if (!
|
|
1109
|
-
|
|
1262
|
+
if (!fs5.existsSync(dir)) {
|
|
1263
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1110
1264
|
}
|
|
1111
|
-
|
|
1265
|
+
fs5.writeFileSync(this.oauthSessionFilePath, JSON.stringify(updatedAuth, null, 2), {
|
|
1112
1266
|
encoding: "utf-8",
|
|
1113
1267
|
mode: 384
|
|
1114
1268
|
});
|
|
1115
|
-
|
|
1269
|
+
logger6.info("Access token refreshed", { expiresAt: newExpiresAt });
|
|
1116
1270
|
return tokenData.access_token;
|
|
1117
1271
|
} catch (error) {
|
|
1118
|
-
|
|
1272
|
+
logger6.error("Token refresh request failed", {
|
|
1119
1273
|
error: error instanceof Error ? error.message : String(error)
|
|
1120
1274
|
});
|
|
1121
1275
|
return null;
|
|
@@ -1127,10 +1281,10 @@ var AuthService = class {
|
|
|
1127
1281
|
* @returns Valid access token or null if not authenticated or refresh failed.
|
|
1128
1282
|
*/
|
|
1129
1283
|
async getValidAccessToken() {
|
|
1130
|
-
const
|
|
1284
|
+
const logger6 = getLogger();
|
|
1131
1285
|
const storedAuth = this.loadStoredAuth();
|
|
1132
1286
|
if (!storedAuth) {
|
|
1133
|
-
|
|
1287
|
+
logger6.debug("No stored auth, cannot get valid token");
|
|
1134
1288
|
return null;
|
|
1135
1289
|
}
|
|
1136
1290
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1140,36 +1294,36 @@ var AuthService = class {
|
|
|
1140
1294
|
return storedAuth.accessToken;
|
|
1141
1295
|
}
|
|
1142
1296
|
if (!isStrictlyExpired) {
|
|
1143
|
-
|
|
1297
|
+
logger6.debug("Access token in buffer zone, attempting proactive refresh");
|
|
1144
1298
|
} else {
|
|
1145
|
-
|
|
1299
|
+
logger6.info("Access token expired, attempting refresh");
|
|
1146
1300
|
}
|
|
1147
1301
|
const refreshedToken = await this.refreshAccessToken();
|
|
1148
1302
|
if (refreshedToken) {
|
|
1149
1303
|
return refreshedToken;
|
|
1150
1304
|
}
|
|
1151
1305
|
if (!isStrictlyExpired) {
|
|
1152
|
-
|
|
1306
|
+
logger6.warn("Token refresh failed, but token not yet expired - using existing token");
|
|
1153
1307
|
return storedAuth.accessToken;
|
|
1154
1308
|
}
|
|
1155
|
-
|
|
1309
|
+
logger6.warn("Token refresh failed and token is expired, user needs to re-authenticate");
|
|
1156
1310
|
return null;
|
|
1157
1311
|
}
|
|
1158
1312
|
/**
|
|
1159
1313
|
* Clear stored authentication (logout).
|
|
1160
1314
|
*/
|
|
1161
1315
|
logout() {
|
|
1162
|
-
const
|
|
1163
|
-
if (!
|
|
1164
|
-
|
|
1316
|
+
const logger6 = getLogger();
|
|
1317
|
+
if (!fs5.existsSync(this.oauthSessionFilePath)) {
|
|
1318
|
+
logger6.debug("No auth to clear");
|
|
1165
1319
|
return false;
|
|
1166
1320
|
}
|
|
1167
1321
|
try {
|
|
1168
|
-
|
|
1169
|
-
|
|
1322
|
+
fs5.unlinkSync(this.oauthSessionFilePath);
|
|
1323
|
+
logger6.info("Auth cleared successfully");
|
|
1170
1324
|
return true;
|
|
1171
1325
|
} catch (error) {
|
|
1172
|
-
|
|
1326
|
+
logger6.error("Failed to clear auth", {
|
|
1173
1327
|
error: error instanceof Error ? error.message : String(error)
|
|
1174
1328
|
});
|
|
1175
1329
|
return false;
|
|
@@ -1185,9 +1339,9 @@ function resetAuthService() {
|
|
|
1185
1339
|
serviceInstance = null;
|
|
1186
1340
|
}
|
|
1187
1341
|
function sleep(params) {
|
|
1188
|
-
return new Promise((
|
|
1342
|
+
return new Promise((resolve3) => {
|
|
1189
1343
|
setTimeout(() => {
|
|
1190
|
-
|
|
1344
|
+
resolve3();
|
|
1191
1345
|
}, params.durationMs);
|
|
1192
1346
|
});
|
|
1193
1347
|
}
|
|
@@ -1209,14 +1363,14 @@ var StorageService = class {
|
|
|
1209
1363
|
* Ensure the base directories exist.
|
|
1210
1364
|
*/
|
|
1211
1365
|
ensureDirectories() {
|
|
1212
|
-
const
|
|
1213
|
-
if (!
|
|
1214
|
-
|
|
1215
|
-
|
|
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 });
|
|
1216
1370
|
}
|
|
1217
|
-
if (!
|
|
1218
|
-
|
|
1219
|
-
|
|
1371
|
+
if (!fs5.existsSync(this.sessionsDir)) {
|
|
1372
|
+
fs5.mkdirSync(this.sessionsDir, { recursive: true });
|
|
1373
|
+
logger6.info("Created sessions directory", { path: this.sessionsDir });
|
|
1220
1374
|
}
|
|
1221
1375
|
}
|
|
1222
1376
|
/**
|
|
@@ -1225,14 +1379,14 @@ var StorageService = class {
|
|
|
1225
1379
|
* @returns Path to the session directory.
|
|
1226
1380
|
*/
|
|
1227
1381
|
createSessionDirectory(sessionId) {
|
|
1228
|
-
const
|
|
1382
|
+
const logger6 = getLogger();
|
|
1229
1383
|
this.ensureDirectories();
|
|
1230
1384
|
const sessionDir = path2.join(this.sessionsDir, sessionId);
|
|
1231
|
-
if (!
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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 });
|
|
1236
1390
|
}
|
|
1237
1391
|
return sessionDir;
|
|
1238
1392
|
}
|
|
@@ -1241,11 +1395,11 @@ var StorageService = class {
|
|
|
1241
1395
|
* @param metadata - Session metadata to save.
|
|
1242
1396
|
*/
|
|
1243
1397
|
saveSessionMetadata(metadata) {
|
|
1244
|
-
const
|
|
1398
|
+
const logger6 = getLogger();
|
|
1245
1399
|
const sessionDir = this.createSessionDirectory(metadata.sessionId);
|
|
1246
1400
|
const metadataPath = path2.join(sessionDir, "metadata.json");
|
|
1247
|
-
|
|
1248
|
-
|
|
1401
|
+
fs5.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
1402
|
+
logger6.debug("Saved session metadata", { sessionId: metadata.sessionId });
|
|
1249
1403
|
}
|
|
1250
1404
|
/**
|
|
1251
1405
|
* Load session metadata.
|
|
@@ -1253,16 +1407,16 @@ var StorageService = class {
|
|
|
1253
1407
|
* @returns Session metadata, or null if not found.
|
|
1254
1408
|
*/
|
|
1255
1409
|
loadSessionMetadata(sessionId) {
|
|
1256
|
-
const
|
|
1410
|
+
const logger6 = getLogger();
|
|
1257
1411
|
const metadataPath = path2.join(this.sessionsDir, sessionId, "metadata.json");
|
|
1258
|
-
if (!
|
|
1412
|
+
if (!fs5.existsSync(metadataPath)) {
|
|
1259
1413
|
return null;
|
|
1260
1414
|
}
|
|
1261
1415
|
try {
|
|
1262
|
-
const content =
|
|
1416
|
+
const content = fs5.readFileSync(metadataPath, "utf-8");
|
|
1263
1417
|
return JSON.parse(content);
|
|
1264
1418
|
} catch (error) {
|
|
1265
|
-
|
|
1419
|
+
logger6.error("Failed to load session metadata", {
|
|
1266
1420
|
sessionId,
|
|
1267
1421
|
error: error instanceof Error ? error.message : String(error)
|
|
1268
1422
|
});
|
|
@@ -1274,12 +1428,12 @@ var StorageService = class {
|
|
|
1274
1428
|
* @returns Array of session IDs.
|
|
1275
1429
|
*/
|
|
1276
1430
|
listSessions() {
|
|
1277
|
-
if (!
|
|
1431
|
+
if (!fs5.existsSync(this.sessionsDir)) {
|
|
1278
1432
|
return [];
|
|
1279
1433
|
}
|
|
1280
|
-
return
|
|
1434
|
+
return fs5.readdirSync(this.sessionsDir).filter((entry) => {
|
|
1281
1435
|
const entryPath = path2.join(this.sessionsDir, entry);
|
|
1282
|
-
return
|
|
1436
|
+
return fs5.statSync(entryPath).isDirectory();
|
|
1283
1437
|
});
|
|
1284
1438
|
}
|
|
1285
1439
|
/**
|
|
@@ -1288,11 +1442,11 @@ var StorageService = class {
|
|
|
1288
1442
|
*/
|
|
1289
1443
|
getCurrentSessionId() {
|
|
1290
1444
|
const currentPath = path2.join(this.sessionsDir, "current");
|
|
1291
|
-
if (!
|
|
1445
|
+
if (!fs5.existsSync(currentPath)) {
|
|
1292
1446
|
return null;
|
|
1293
1447
|
}
|
|
1294
1448
|
try {
|
|
1295
|
-
const target =
|
|
1449
|
+
const target = fs5.readlinkSync(currentPath);
|
|
1296
1450
|
return path2.basename(target);
|
|
1297
1451
|
} catch {
|
|
1298
1452
|
return null;
|
|
@@ -1303,14 +1457,14 @@ var StorageService = class {
|
|
|
1303
1457
|
* @param sessionId - Session ID to set as current.
|
|
1304
1458
|
*/
|
|
1305
1459
|
setCurrentSession(sessionId) {
|
|
1306
|
-
const
|
|
1460
|
+
const logger6 = getLogger();
|
|
1307
1461
|
const currentPath = path2.join(this.sessionsDir, "current");
|
|
1308
1462
|
const targetPath = path2.join(this.sessionsDir, sessionId);
|
|
1309
|
-
if (
|
|
1310
|
-
|
|
1463
|
+
if (fs5.existsSync(currentPath)) {
|
|
1464
|
+
fs5.unlinkSync(currentPath);
|
|
1311
1465
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1466
|
+
fs5.symlinkSync(targetPath, currentPath);
|
|
1467
|
+
logger6.info("Set current session", { sessionId });
|
|
1314
1468
|
}
|
|
1315
1469
|
/**
|
|
1316
1470
|
* Save a screenshot to the session directory.
|
|
@@ -1318,11 +1472,11 @@ var StorageService = class {
|
|
|
1318
1472
|
*/
|
|
1319
1473
|
saveScreenshot(params) {
|
|
1320
1474
|
const { sessionId, filename, data } = params;
|
|
1321
|
-
const
|
|
1475
|
+
const logger6 = getLogger();
|
|
1322
1476
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1323
1477
|
const screenshotPath = path2.join(sessionDir, "screenshots", filename);
|
|
1324
|
-
|
|
1325
|
-
|
|
1478
|
+
fs5.writeFileSync(screenshotPath, data);
|
|
1479
|
+
logger6.debug("Saved screenshot", { sessionId, filename });
|
|
1326
1480
|
return screenshotPath;
|
|
1327
1481
|
}
|
|
1328
1482
|
/**
|
|
@@ -1331,11 +1485,11 @@ var StorageService = class {
|
|
|
1331
1485
|
*/
|
|
1332
1486
|
appendToResults(params) {
|
|
1333
1487
|
const { sessionId, content } = params;
|
|
1334
|
-
const
|
|
1488
|
+
const logger6 = getLogger();
|
|
1335
1489
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1336
1490
|
const resultsPath = path2.join(sessionDir, "results.md");
|
|
1337
|
-
|
|
1338
|
-
|
|
1491
|
+
fs5.appendFileSync(resultsPath, content + "\n", "utf-8");
|
|
1492
|
+
logger6.debug("Appended to results", { sessionId });
|
|
1339
1493
|
}
|
|
1340
1494
|
/**
|
|
1341
1495
|
* Get the results markdown content.
|
|
@@ -1344,10 +1498,10 @@ var StorageService = class {
|
|
|
1344
1498
|
*/
|
|
1345
1499
|
getResults(sessionId) {
|
|
1346
1500
|
const resultsPath = path2.join(this.sessionsDir, sessionId, "results.md");
|
|
1347
|
-
if (!
|
|
1501
|
+
if (!fs5.existsSync(resultsPath)) {
|
|
1348
1502
|
return null;
|
|
1349
1503
|
}
|
|
1350
|
-
return
|
|
1504
|
+
return fs5.readFileSync(resultsPath, "utf-8");
|
|
1351
1505
|
}
|
|
1352
1506
|
/**
|
|
1353
1507
|
* Get the data directory path.
|
|
@@ -1370,7 +1524,7 @@ var StorageService = class {
|
|
|
1370
1524
|
*/
|
|
1371
1525
|
createSession(params) {
|
|
1372
1526
|
const { sessionId, targetUrl, testInstructions } = params;
|
|
1373
|
-
const
|
|
1527
|
+
const logger6 = getLogger();
|
|
1374
1528
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1375
1529
|
const metadata = {
|
|
1376
1530
|
sessionId,
|
|
@@ -1382,7 +1536,7 @@ var StorageService = class {
|
|
|
1382
1536
|
};
|
|
1383
1537
|
this.saveSessionMetadata(metadata);
|
|
1384
1538
|
this.setCurrentSession(sessionId);
|
|
1385
|
-
|
|
1539
|
+
logger6.info("Created session", { sessionId, targetUrl });
|
|
1386
1540
|
return sessionDir;
|
|
1387
1541
|
}
|
|
1388
1542
|
/**
|
|
@@ -1391,10 +1545,10 @@ var StorageService = class {
|
|
|
1391
1545
|
*/
|
|
1392
1546
|
updateSessionStatus(params) {
|
|
1393
1547
|
const { sessionId, status } = params;
|
|
1394
|
-
const
|
|
1548
|
+
const logger6 = getLogger();
|
|
1395
1549
|
const metadata = this.loadSessionMetadata(sessionId);
|
|
1396
1550
|
if (!metadata) {
|
|
1397
|
-
|
|
1551
|
+
logger6.warn("Session not found for status update", { sessionId });
|
|
1398
1552
|
return;
|
|
1399
1553
|
}
|
|
1400
1554
|
metadata.status = status;
|
|
@@ -1402,7 +1556,7 @@ var StorageService = class {
|
|
|
1402
1556
|
metadata.endTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1403
1557
|
}
|
|
1404
1558
|
this.saveSessionMetadata(metadata);
|
|
1405
|
-
|
|
1559
|
+
logger6.debug("Updated session status", { sessionId, status });
|
|
1406
1560
|
}
|
|
1407
1561
|
/**
|
|
1408
1562
|
* Initialize the results.md file with a header.
|
|
@@ -1410,10 +1564,10 @@ var StorageService = class {
|
|
|
1410
1564
|
*/
|
|
1411
1565
|
initializeResults(params) {
|
|
1412
1566
|
const { sessionId, targetUrl, testInstructions } = params;
|
|
1413
|
-
const
|
|
1567
|
+
const logger6 = getLogger();
|
|
1414
1568
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1415
1569
|
const resultsPath = path2.join(sessionDir, "results.md");
|
|
1416
|
-
const
|
|
1570
|
+
const header = [
|
|
1417
1571
|
`# Test Results: ${sessionId}`,
|
|
1418
1572
|
"",
|
|
1419
1573
|
`**Target URL:** ${targetUrl}`,
|
|
@@ -1425,8 +1579,8 @@ var StorageService = class {
|
|
|
1425
1579
|
"## Test Steps",
|
|
1426
1580
|
""
|
|
1427
1581
|
].join("\n");
|
|
1428
|
-
|
|
1429
|
-
|
|
1582
|
+
fs5.writeFileSync(resultsPath, header, "utf-8");
|
|
1583
|
+
logger6.debug("Initialized results.md", { sessionId });
|
|
1430
1584
|
}
|
|
1431
1585
|
/**
|
|
1432
1586
|
* Append a test step to the results.md file.
|
|
@@ -1434,7 +1588,7 @@ var StorageService = class {
|
|
|
1434
1588
|
*/
|
|
1435
1589
|
appendStepToResults(params) {
|
|
1436
1590
|
const { sessionId, step } = params;
|
|
1437
|
-
const
|
|
1591
|
+
const logger6 = getLogger();
|
|
1438
1592
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1439
1593
|
const resultsPath = path2.join(sessionDir, "results.md");
|
|
1440
1594
|
const statusIcon = step.success ? "\u2713" : "\u2717";
|
|
@@ -1446,8 +1600,8 @@ var StorageService = class {
|
|
|
1446
1600
|
step.screenshotPath ? `- **Screenshot:** [step-${String(step.stepNumber).padStart(3, "0")}.png](screenshots/step-${String(step.stepNumber).padStart(3, "0")}.png)` : "",
|
|
1447
1601
|
""
|
|
1448
1602
|
].filter(Boolean).join("\n");
|
|
1449
|
-
|
|
1450
|
-
|
|
1603
|
+
fs5.appendFileSync(resultsPath, stepContent + "\n", "utf-8");
|
|
1604
|
+
logger6.debug("Appended step to results", { sessionId, stepNumber: step.stepNumber });
|
|
1451
1605
|
const metadata = this.loadSessionMetadata(sessionId);
|
|
1452
1606
|
if (metadata) {
|
|
1453
1607
|
metadata.stepsCount = (metadata.stepsCount ?? 0) + 1;
|
|
@@ -1460,11 +1614,11 @@ var StorageService = class {
|
|
|
1460
1614
|
*/
|
|
1461
1615
|
finalizeResults(params) {
|
|
1462
1616
|
const { sessionId, status, summary } = params;
|
|
1463
|
-
const
|
|
1617
|
+
const logger6 = getLogger();
|
|
1464
1618
|
const sessionDir = path2.join(this.sessionsDir, sessionId);
|
|
1465
1619
|
const resultsPath = path2.join(sessionDir, "results.md");
|
|
1466
|
-
if (!
|
|
1467
|
-
|
|
1620
|
+
if (!fs5.existsSync(resultsPath)) {
|
|
1621
|
+
logger6.warn("Results file not found for finalization", { sessionId });
|
|
1468
1622
|
return;
|
|
1469
1623
|
}
|
|
1470
1624
|
const metadata = this.loadSessionMetadata(sessionId);
|
|
@@ -1486,8 +1640,8 @@ var StorageService = class {
|
|
|
1486
1640
|
"",
|
|
1487
1641
|
summary ? summary : ""
|
|
1488
1642
|
].join("\n");
|
|
1489
|
-
|
|
1490
|
-
|
|
1643
|
+
fs5.appendFileSync(resultsPath, footer, "utf-8");
|
|
1644
|
+
logger6.debug("Finalized results.md", { sessionId, status });
|
|
1491
1645
|
if (metadata) {
|
|
1492
1646
|
metadata.durationMs = durationMs;
|
|
1493
1647
|
metadata.endTime = endTime.toISOString();
|
|
@@ -1529,7 +1683,7 @@ var StorageService = class {
|
|
|
1529
1683
|
*/
|
|
1530
1684
|
cleanupOldSessions(params) {
|
|
1531
1685
|
const maxAgeDays = params?.maxAgeDays ?? DEFAULT_SESSION_MAX_AGE_DAYS;
|
|
1532
|
-
const
|
|
1686
|
+
const logger6 = getLogger();
|
|
1533
1687
|
const cutoffDate = /* @__PURE__ */ new Date();
|
|
1534
1688
|
cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
|
|
1535
1689
|
const sessionIds = this.listSessions();
|
|
@@ -1546,14 +1700,14 @@ var StorageService = class {
|
|
|
1546
1700
|
if (sessionDate < cutoffDate) {
|
|
1547
1701
|
const sessionDir = path2.join(this.sessionsDir, sessionId);
|
|
1548
1702
|
try {
|
|
1549
|
-
|
|
1703
|
+
fs5.rmSync(sessionDir, { recursive: true, force: true });
|
|
1550
1704
|
deletedCount++;
|
|
1551
|
-
|
|
1705
|
+
logger6.info("Deleted old session", {
|
|
1552
1706
|
sessionId,
|
|
1553
1707
|
age: Math.floor((Date.now() - sessionDate.getTime()) / (1e3 * 60 * 60 * 24))
|
|
1554
1708
|
});
|
|
1555
1709
|
} catch (error) {
|
|
1556
|
-
|
|
1710
|
+
logger6.error("Failed to delete session", {
|
|
1557
1711
|
sessionId,
|
|
1558
1712
|
error: error instanceof Error ? error.message : String(error)
|
|
1559
1713
|
});
|
|
@@ -1561,7 +1715,7 @@ var StorageService = class {
|
|
|
1561
1715
|
}
|
|
1562
1716
|
}
|
|
1563
1717
|
if (deletedCount > 0) {
|
|
1564
|
-
|
|
1718
|
+
logger6.info("Session cleanup completed", {
|
|
1565
1719
|
deletedCount,
|
|
1566
1720
|
maxAgeDays
|
|
1567
1721
|
});
|
|
@@ -1582,25 +1736,25 @@ var StorageService = class {
|
|
|
1582
1736
|
* @returns Whether deletion succeeded.
|
|
1583
1737
|
*/
|
|
1584
1738
|
deleteSession(sessionId) {
|
|
1585
|
-
const
|
|
1739
|
+
const logger6 = getLogger();
|
|
1586
1740
|
const sessionDir = path2.join(this.sessionsDir, sessionId);
|
|
1587
|
-
if (!
|
|
1588
|
-
|
|
1741
|
+
if (!fs5.existsSync(sessionDir)) {
|
|
1742
|
+
logger6.warn("Session not found for deletion", { sessionId });
|
|
1589
1743
|
return false;
|
|
1590
1744
|
}
|
|
1591
1745
|
try {
|
|
1592
1746
|
const currentId = this.getCurrentSessionId();
|
|
1593
1747
|
if (currentId === sessionId) {
|
|
1594
1748
|
const currentPath = path2.join(this.sessionsDir, "current");
|
|
1595
|
-
if (
|
|
1596
|
-
|
|
1749
|
+
if (fs5.existsSync(currentPath)) {
|
|
1750
|
+
fs5.unlinkSync(currentPath);
|
|
1597
1751
|
}
|
|
1598
1752
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1753
|
+
fs5.rmSync(sessionDir, { recursive: true, force: true });
|
|
1754
|
+
logger6.info("Deleted session", { sessionId });
|
|
1601
1755
|
return true;
|
|
1602
1756
|
} catch (error) {
|
|
1603
|
-
|
|
1757
|
+
logger6.error("Failed to delete session", {
|
|
1604
1758
|
sessionId,
|
|
1605
1759
|
error: error instanceof Error ? error.message : String(error)
|
|
1606
1760
|
});
|
|
@@ -1616,7 +1770,7 @@ function getStorageService() {
|
|
|
1616
1770
|
function resetStorageService() {
|
|
1617
1771
|
serviceInstance2 = null;
|
|
1618
1772
|
}
|
|
1619
|
-
var
|
|
1773
|
+
var logger3 = getLogger();
|
|
1620
1774
|
var RunResultStorageService = class {
|
|
1621
1775
|
/** Base directory for run results. */
|
|
1622
1776
|
runResultsDir;
|
|
@@ -1633,11 +1787,11 @@ var RunResultStorageService = class {
|
|
|
1633
1787
|
* Ensure storage directories exist.
|
|
1634
1788
|
*/
|
|
1635
1789
|
ensureDirectories() {
|
|
1636
|
-
if (!
|
|
1637
|
-
|
|
1790
|
+
if (!fs5.existsSync(this.runResultsDir)) {
|
|
1791
|
+
fs5.mkdirSync(this.runResultsDir, { recursive: true });
|
|
1638
1792
|
}
|
|
1639
|
-
if (!
|
|
1640
|
-
|
|
1793
|
+
if (!fs5.existsSync(this.testScriptsDir)) {
|
|
1794
|
+
fs5.mkdirSync(this.testScriptsDir, { recursive: true });
|
|
1641
1795
|
}
|
|
1642
1796
|
}
|
|
1643
1797
|
// ========================================
|
|
@@ -1648,14 +1802,14 @@ var RunResultStorageService = class {
|
|
|
1648
1802
|
*/
|
|
1649
1803
|
listRunResults() {
|
|
1650
1804
|
try {
|
|
1651
|
-
const files =
|
|
1805
|
+
const files = fs5.readdirSync(this.runResultsDir).filter((f) => f.endsWith(".json"));
|
|
1652
1806
|
const results = [];
|
|
1653
1807
|
for (const file of files) {
|
|
1654
1808
|
try {
|
|
1655
|
-
const content =
|
|
1809
|
+
const content = fs5.readFileSync(path2.join(this.runResultsDir, file), "utf-8");
|
|
1656
1810
|
results.push(JSON.parse(content));
|
|
1657
1811
|
} catch {
|
|
1658
|
-
|
|
1812
|
+
logger3.warn("Failed to read run result file", { file });
|
|
1659
1813
|
}
|
|
1660
1814
|
}
|
|
1661
1815
|
return results.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
@@ -1668,11 +1822,11 @@ var RunResultStorageService = class {
|
|
|
1668
1822
|
*/
|
|
1669
1823
|
getRunResult(runId) {
|
|
1670
1824
|
const filePath = path2.join(this.runResultsDir, `${runId}.json`);
|
|
1671
|
-
if (!
|
|
1825
|
+
if (!fs5.existsSync(filePath)) {
|
|
1672
1826
|
return void 0;
|
|
1673
1827
|
}
|
|
1674
1828
|
try {
|
|
1675
|
-
const content =
|
|
1829
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
1676
1830
|
return JSON.parse(content);
|
|
1677
1831
|
} catch {
|
|
1678
1832
|
return void 0;
|
|
@@ -1683,7 +1837,7 @@ var RunResultStorageService = class {
|
|
|
1683
1837
|
*/
|
|
1684
1838
|
saveRunResult(result) {
|
|
1685
1839
|
const filePath = path2.join(this.runResultsDir, `${result.id}.json`);
|
|
1686
|
-
|
|
1840
|
+
fs5.writeFileSync(filePath, JSON.stringify(result, null, 2));
|
|
1687
1841
|
}
|
|
1688
1842
|
/**
|
|
1689
1843
|
* Create a new run result.
|
|
@@ -1731,14 +1885,14 @@ var RunResultStorageService = class {
|
|
|
1731
1885
|
*/
|
|
1732
1886
|
listTestScripts() {
|
|
1733
1887
|
try {
|
|
1734
|
-
const files =
|
|
1888
|
+
const files = fs5.readdirSync(this.testScriptsDir).filter((f) => f.endsWith(".json"));
|
|
1735
1889
|
const scripts = [];
|
|
1736
1890
|
for (const file of files) {
|
|
1737
1891
|
try {
|
|
1738
|
-
const content =
|
|
1892
|
+
const content = fs5.readFileSync(path2.join(this.testScriptsDir, file), "utf-8");
|
|
1739
1893
|
scripts.push(JSON.parse(content));
|
|
1740
1894
|
} catch {
|
|
1741
|
-
|
|
1895
|
+
logger3.warn("Failed to read test script file", { file });
|
|
1742
1896
|
}
|
|
1743
1897
|
}
|
|
1744
1898
|
return scripts.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
@@ -1751,11 +1905,11 @@ var RunResultStorageService = class {
|
|
|
1751
1905
|
*/
|
|
1752
1906
|
getTestScript(testScriptId) {
|
|
1753
1907
|
const filePath = path2.join(this.testScriptsDir, `${testScriptId}.json`);
|
|
1754
|
-
if (!
|
|
1908
|
+
if (!fs5.existsSync(filePath)) {
|
|
1755
1909
|
return void 0;
|
|
1756
1910
|
}
|
|
1757
1911
|
try {
|
|
1758
|
-
const content =
|
|
1912
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
1759
1913
|
return JSON.parse(content);
|
|
1760
1914
|
} catch {
|
|
1761
1915
|
return void 0;
|
|
@@ -1766,7 +1920,7 @@ var RunResultStorageService = class {
|
|
|
1766
1920
|
*/
|
|
1767
1921
|
saveTestScript(script) {
|
|
1768
1922
|
const filePath = path2.join(this.testScriptsDir, `${script.id}.json`);
|
|
1769
|
-
|
|
1923
|
+
fs5.writeFileSync(filePath, JSON.stringify(script, null, 2));
|
|
1770
1924
|
}
|
|
1771
1925
|
/**
|
|
1772
1926
|
* Create a new test script.
|
|
@@ -1814,7 +1968,7 @@ function getRunResultStorageService() {
|
|
|
1814
1968
|
function resetRunResultStorageService() {
|
|
1815
1969
|
instance = null;
|
|
1816
1970
|
}
|
|
1817
|
-
var
|
|
1971
|
+
var logger4 = getLogger();
|
|
1818
1972
|
var activeProcesses = /* @__PURE__ */ new Map();
|
|
1819
1973
|
function getAuthenticatedUserId() {
|
|
1820
1974
|
const authService = getAuthService();
|
|
@@ -1837,7 +1991,7 @@ async function findPackageJsonAsync() {
|
|
|
1837
1991
|
];
|
|
1838
1992
|
for (const candidatePath of candidatePaths) {
|
|
1839
1993
|
try {
|
|
1840
|
-
const packageJsonRaw = await
|
|
1994
|
+
const packageJsonRaw = await fs7.readFile(candidatePath, "utf-8");
|
|
1841
1995
|
const packageJson = JSON.parse(packageJsonRaw);
|
|
1842
1996
|
if (packageJson.name === "@muggleai/works" && packageJson.version && packageJson.muggleConfig?.electronAppVersion) {
|
|
1843
1997
|
return {
|
|
@@ -1884,19 +2038,19 @@ function buildStudioAuthContent() {
|
|
|
1884
2038
|
async function ensureTempDir() {
|
|
1885
2039
|
const config = getConfig();
|
|
1886
2040
|
const tempDir = path2.join(config.localQa.dataDir, "temp");
|
|
1887
|
-
await
|
|
2041
|
+
await fs7.mkdir(tempDir, { recursive: true });
|
|
1888
2042
|
return tempDir;
|
|
1889
2043
|
}
|
|
1890
2044
|
async function writeTempFile(params) {
|
|
1891
2045
|
const tempDir = await ensureTempDir();
|
|
1892
2046
|
const filePath = path2.join(tempDir, params.filename);
|
|
1893
|
-
await
|
|
2047
|
+
await fs7.writeFile(filePath, JSON.stringify(params.data, null, 2));
|
|
1894
2048
|
return filePath;
|
|
1895
2049
|
}
|
|
1896
2050
|
async function cleanupTempFiles(params) {
|
|
1897
2051
|
for (const filePath of params.filePaths) {
|
|
1898
2052
|
try {
|
|
1899
|
-
await
|
|
2053
|
+
await fs7.unlink(filePath);
|
|
1900
2054
|
} catch {
|
|
1901
2055
|
}
|
|
1902
2056
|
}
|
|
@@ -1906,10 +2060,10 @@ async function moveResultsToArtifacts(params) {
|
|
|
1906
2060
|
const sessionsDir = storageService.getSessionsDir();
|
|
1907
2061
|
const artifactsDir = path2.join(sessionsDir, params.runId);
|
|
1908
2062
|
const actionScriptPath = path2.join(artifactsDir, "action-script.json");
|
|
1909
|
-
await
|
|
1910
|
-
await
|
|
2063
|
+
await fs7.mkdir(artifactsDir, { recursive: true });
|
|
2064
|
+
await fs7.copyFile(params.generatedScriptPath, actionScriptPath);
|
|
1911
2065
|
try {
|
|
1912
|
-
await
|
|
2066
|
+
await fs7.unlink(params.generatedScriptPath);
|
|
1913
2067
|
} catch {
|
|
1914
2068
|
}
|
|
1915
2069
|
return artifactsDir;
|
|
@@ -1918,14 +2072,14 @@ async function writeExecutionLogs(params) {
|
|
|
1918
2072
|
const storageService = getStorageService();
|
|
1919
2073
|
const sessionsDir = storageService.getSessionsDir();
|
|
1920
2074
|
const artifactsDir = path2.join(sessionsDir, params.runId);
|
|
1921
|
-
await
|
|
2075
|
+
await fs7.mkdir(artifactsDir, { recursive: true });
|
|
1922
2076
|
const stdoutPath = path2.join(artifactsDir, "stdout.log");
|
|
1923
2077
|
const stderrPath = path2.join(artifactsDir, "stderr.log");
|
|
1924
2078
|
await Promise.all([
|
|
1925
|
-
|
|
1926
|
-
|
|
2079
|
+
fs7.writeFile(stdoutPath, params.stdout, "utf-8"),
|
|
2080
|
+
fs7.writeFile(stderrPath, params.stderr, "utf-8")
|
|
1927
2081
|
]);
|
|
1928
|
-
|
|
2082
|
+
logger4.info("Wrote execution logs to artifacts directory", {
|
|
1929
2083
|
runId: params.runId,
|
|
1930
2084
|
artifactsDir
|
|
1931
2085
|
});
|
|
@@ -2057,7 +2211,7 @@ async function executeElectronAppAsync(params) {
|
|
|
2057
2211
|
if (params.showUi) {
|
|
2058
2212
|
spawnArgs.push("--show-ui");
|
|
2059
2213
|
}
|
|
2060
|
-
|
|
2214
|
+
logger4.info("Spawning electron-app for local execution", {
|
|
2061
2215
|
runId: params.runId,
|
|
2062
2216
|
mode,
|
|
2063
2217
|
electronAppPath,
|
|
@@ -2079,7 +2233,7 @@ async function executeElectronAppAsync(params) {
|
|
|
2079
2233
|
capturedStderr: ""
|
|
2080
2234
|
};
|
|
2081
2235
|
activeProcesses.set(params.runId, processInfo);
|
|
2082
|
-
return await new Promise((
|
|
2236
|
+
return await new Promise((resolve3, reject) => {
|
|
2083
2237
|
let settled = false;
|
|
2084
2238
|
const finalize = (result) => {
|
|
2085
2239
|
if (settled) {
|
|
@@ -2091,7 +2245,7 @@ async function executeElectronAppAsync(params) {
|
|
|
2091
2245
|
}
|
|
2092
2246
|
activeProcesses.delete(params.runId);
|
|
2093
2247
|
if (result.ok) {
|
|
2094
|
-
|
|
2248
|
+
resolve3(result.payload);
|
|
2095
2249
|
} else {
|
|
2096
2250
|
reject(result.payload);
|
|
2097
2251
|
}
|
|
@@ -2245,7 +2399,7 @@ ${executionResult.stderr}`;
|
|
|
2245
2399
|
path2.dirname(inputFilePath),
|
|
2246
2400
|
`gen_${path2.basename(inputFilePath)}`
|
|
2247
2401
|
);
|
|
2248
|
-
const generatedScriptRaw = await
|
|
2402
|
+
const generatedScriptRaw = await fs7.readFile(generatedScriptPath, "utf-8");
|
|
2249
2403
|
const generatedScript = JSON.parse(generatedScriptRaw);
|
|
2250
2404
|
const generatedSteps = generatedScript.steps;
|
|
2251
2405
|
if (!Array.isArray(generatedSteps)) {
|
|
@@ -2452,114 +2606,13 @@ function listActiveExecutions() {
|
|
|
2452
2606
|
status: process2.status
|
|
2453
2607
|
}));
|
|
2454
2608
|
}
|
|
2455
|
-
var API_KEY_FILE2 = "api-key.json";
|
|
2456
|
-
function getApiKeyFilePath() {
|
|
2457
|
-
return path2.join(getDataDir(), API_KEY_FILE2);
|
|
2458
|
-
}
|
|
2459
|
-
function ensureDataDir() {
|
|
2460
|
-
const dataDir = getDataDir();
|
|
2461
|
-
if (!fs3.existsSync(dataDir)) {
|
|
2462
|
-
fs3.mkdirSync(dataDir, { recursive: true });
|
|
2463
|
-
}
|
|
2464
|
-
}
|
|
2465
|
-
function loadApiKeyData() {
|
|
2466
|
-
const logger14 = getLogger();
|
|
2467
|
-
const apiKeyPath = getApiKeyFilePath();
|
|
2468
|
-
try {
|
|
2469
|
-
if (!fs3.existsSync(apiKeyPath)) {
|
|
2470
|
-
logger14.debug("No API key file found", { path: apiKeyPath });
|
|
2471
|
-
return null;
|
|
2472
|
-
}
|
|
2473
|
-
const content = fs3.readFileSync(apiKeyPath, "utf-8");
|
|
2474
|
-
const data = JSON.parse(content);
|
|
2475
|
-
return data;
|
|
2476
|
-
} catch (error) {
|
|
2477
|
-
logger14.warn("Failed to load API key data", {
|
|
2478
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2479
|
-
});
|
|
2480
|
-
return null;
|
|
2481
|
-
}
|
|
2482
|
-
}
|
|
2483
|
-
function saveApiKeyData(data) {
|
|
2484
|
-
const logger14 = getLogger();
|
|
2485
|
-
const apiKeyPath = getApiKeyFilePath();
|
|
2486
|
-
try {
|
|
2487
|
-
ensureDataDir();
|
|
2488
|
-
const content = JSON.stringify(data, null, 2);
|
|
2489
|
-
fs3.writeFileSync(apiKeyPath, content, { mode: 384 });
|
|
2490
|
-
logger14.info("API key saved", { path: apiKeyPath });
|
|
2491
|
-
} catch (error) {
|
|
2492
|
-
logger14.error("Failed to save API key", {
|
|
2493
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2494
|
-
});
|
|
2495
|
-
throw error;
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
|
-
function deleteApiKeyData() {
|
|
2499
|
-
const logger14 = getLogger();
|
|
2500
|
-
const apiKeyPath = getApiKeyFilePath();
|
|
2501
|
-
try {
|
|
2502
|
-
if (fs3.existsSync(apiKeyPath)) {
|
|
2503
|
-
fs3.unlinkSync(apiKeyPath);
|
|
2504
|
-
logger14.info("API key deleted", { path: apiKeyPath });
|
|
2505
|
-
}
|
|
2506
|
-
} catch (error) {
|
|
2507
|
-
logger14.warn("Failed to delete API key", {
|
|
2508
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2509
|
-
});
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
|
-
function getValidApiKeyData() {
|
|
2513
|
-
const data = loadApiKeyData();
|
|
2514
|
-
if (!data) {
|
|
2515
|
-
return null;
|
|
2516
|
-
}
|
|
2517
|
-
if (data.apiKey) {
|
|
2518
|
-
return data;
|
|
2519
|
-
}
|
|
2520
|
-
return null;
|
|
2521
|
-
}
|
|
2522
|
-
function hasApiKey() {
|
|
2523
|
-
const data = loadApiKeyData();
|
|
2524
|
-
return !!data?.apiKey;
|
|
2525
|
-
}
|
|
2526
|
-
function getApiKey() {
|
|
2527
|
-
const data = loadApiKeyData();
|
|
2528
|
-
return data?.apiKey ?? null;
|
|
2529
|
-
}
|
|
2530
|
-
function saveApiKey(params) {
|
|
2531
|
-
const logger14 = getLogger();
|
|
2532
|
-
const apiKeyPath = getApiKeyFilePath();
|
|
2533
|
-
try {
|
|
2534
|
-
ensureDataDir();
|
|
2535
|
-
const data = {
|
|
2536
|
-
accessToken: "",
|
|
2537
|
-
expiresAt: "",
|
|
2538
|
-
apiKey: params.apiKey,
|
|
2539
|
-
apiKeyId: params.apiKeyId
|
|
2540
|
-
};
|
|
2541
|
-
const content = JSON.stringify(data, null, 2);
|
|
2542
|
-
fs3.writeFileSync(apiKeyPath, content, { mode: 384 });
|
|
2543
|
-
logger14.info("API key saved", { path: apiKeyPath });
|
|
2544
|
-
} catch (error) {
|
|
2545
|
-
logger14.error("Failed to save API key", {
|
|
2546
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2547
|
-
});
|
|
2548
|
-
throw error;
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
var loadCredentials = loadApiKeyData;
|
|
2552
|
-
var saveCredentials = saveApiKeyData;
|
|
2553
|
-
var deleteCredentials = deleteApiKeyData;
|
|
2554
|
-
var getValidCredentials = getValidApiKeyData;
|
|
2555
|
-
var getCredentialsFilePath = getApiKeyFilePath;
|
|
2556
2609
|
|
|
2557
2610
|
// packages/mcps/src/shared/auth.ts
|
|
2558
|
-
var
|
|
2559
|
-
async function startDeviceCodeFlow(config) {
|
|
2611
|
+
var logger5 = getLogger();
|
|
2612
|
+
async function startDeviceCodeFlow(config, options) {
|
|
2560
2613
|
const deviceCodeUrl = `https://${config.domain}/oauth/device/code`;
|
|
2561
2614
|
try {
|
|
2562
|
-
|
|
2615
|
+
logger5.info("[Auth] Starting device code flow", {
|
|
2563
2616
|
domain: config.domain,
|
|
2564
2617
|
clientId: config.clientId
|
|
2565
2618
|
});
|
|
@@ -2577,17 +2630,25 @@ async function startDeviceCodeFlow(config) {
|
|
|
2577
2630
|
}
|
|
2578
2631
|
);
|
|
2579
2632
|
const data = response.data;
|
|
2580
|
-
|
|
2633
|
+
logger5.info("[Auth] Device code flow started successfully", {
|
|
2581
2634
|
userCode: data.user_code,
|
|
2582
2635
|
expiresIn: data.expires_in
|
|
2583
2636
|
});
|
|
2637
|
+
let browserUrl = data.verification_uri_complete;
|
|
2638
|
+
if (options?.forceNewSession) {
|
|
2639
|
+
const logoutUrl = new URL(`https://${config.domain}/v2/logout`);
|
|
2640
|
+
logoutUrl.searchParams.set("client_id", config.clientId);
|
|
2641
|
+
logoutUrl.searchParams.set("returnTo", data.verification_uri_complete);
|
|
2642
|
+
browserUrl = logoutUrl.toString();
|
|
2643
|
+
logger5.info("[Auth] Force new session: opening logout-redirect URL");
|
|
2644
|
+
}
|
|
2584
2645
|
const browserOpenResult = await openBrowserUrl({
|
|
2585
|
-
url:
|
|
2646
|
+
url: browserUrl
|
|
2586
2647
|
});
|
|
2587
2648
|
if (browserOpenResult.opened) {
|
|
2588
|
-
|
|
2649
|
+
logger5.info("[Auth] Browser opened for device code login");
|
|
2589
2650
|
} else {
|
|
2590
|
-
|
|
2651
|
+
logger5.warn("[Auth] Failed to open browser", {
|
|
2591
2652
|
error: browserOpenResult.error,
|
|
2592
2653
|
verificationUriComplete: data.verification_uri_complete
|
|
2593
2654
|
});
|
|
@@ -2604,7 +2665,7 @@ async function startDeviceCodeFlow(config) {
|
|
|
2604
2665
|
};
|
|
2605
2666
|
} catch (error) {
|
|
2606
2667
|
if (error instanceof AxiosError) {
|
|
2607
|
-
|
|
2668
|
+
logger5.error("[Auth] Failed to start device code flow", {
|
|
2608
2669
|
status: error.response?.status,
|
|
2609
2670
|
data: error.response?.data
|
|
2610
2671
|
});
|
|
@@ -2632,7 +2693,7 @@ async function pollDeviceCode(config, deviceCode) {
|
|
|
2632
2693
|
}
|
|
2633
2694
|
}
|
|
2634
2695
|
);
|
|
2635
|
-
|
|
2696
|
+
logger5.info("[Auth] Authorization successful");
|
|
2636
2697
|
return {
|
|
2637
2698
|
status: "authorized",
|
|
2638
2699
|
accessToken: response.data.access_token,
|
|
@@ -2671,7 +2732,7 @@ async function pollDeviceCode(config, deviceCode) {
|
|
|
2671
2732
|
errorDescription: data.error_description || "User denied access"
|
|
2672
2733
|
};
|
|
2673
2734
|
}
|
|
2674
|
-
|
|
2735
|
+
logger5.error("[Auth] Unexpected error during poll", {
|
|
2675
2736
|
status: error.response.status,
|
|
2676
2737
|
error: errorCode,
|
|
2677
2738
|
description: data.error_description
|
|
@@ -2688,7 +2749,7 @@ async function createApiKeyWithToken(accessToken, keyName, expiry = "90d") {
|
|
|
2688
2749
|
const config = getConfig();
|
|
2689
2750
|
const apiKeyUrl = `${config.e2e.promptServiceBaseUrl}/v1/protected/api-keys`;
|
|
2690
2751
|
try {
|
|
2691
|
-
|
|
2752
|
+
logger5.info("[Auth] Creating API key", {
|
|
2692
2753
|
keyName,
|
|
2693
2754
|
expiry
|
|
2694
2755
|
});
|
|
@@ -2705,13 +2766,13 @@ async function createApiKeyWithToken(accessToken, keyName, expiry = "90d") {
|
|
|
2705
2766
|
}
|
|
2706
2767
|
}
|
|
2707
2768
|
);
|
|
2708
|
-
|
|
2769
|
+
logger5.info("[Auth] API key created successfully", {
|
|
2709
2770
|
keyId: response.data.id
|
|
2710
2771
|
});
|
|
2711
2772
|
return response.data;
|
|
2712
2773
|
} catch (error) {
|
|
2713
2774
|
if (error instanceof AxiosError) {
|
|
2714
|
-
|
|
2775
|
+
logger5.error("[Auth] Failed to create API key", {
|
|
2715
2776
|
status: error.response?.status,
|
|
2716
2777
|
data: error.response?.data
|
|
2717
2778
|
});
|
|
@@ -2751,7 +2812,7 @@ async function performLogin(keyName, keyExpiry = "90d", timeoutMs = 12e4) {
|
|
|
2751
2812
|
userId: storedAuth?.userId
|
|
2752
2813
|
};
|
|
2753
2814
|
if (keyName && storedAuth?.accessToken) {
|
|
2754
|
-
|
|
2815
|
+
logger5.info("[Auth] Creating API key as explicitly requested", {
|
|
2755
2816
|
keyName
|
|
2756
2817
|
});
|
|
2757
2818
|
const apiKeyResult = await createApiKeyWithToken(
|
|
@@ -2799,7 +2860,7 @@ function performLogout() {
|
|
|
2799
2860
|
const authService = getAuthService();
|
|
2800
2861
|
authService.logout();
|
|
2801
2862
|
deleteApiKeyData();
|
|
2802
|
-
|
|
2863
|
+
logger5.info("[Auth] Logged out successfully");
|
|
2803
2864
|
}
|
|
2804
2865
|
function getCallerCredentials() {
|
|
2805
2866
|
const apiKeyData = getValidApiKeyData();
|
|
@@ -2879,6 +2940,34 @@ function toolRequiresAuth(toolName) {
|
|
|
2879
2940
|
];
|
|
2880
2941
|
return !noAuthTools.includes(toolName);
|
|
2881
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
|
+
});
|
|
2882
2971
|
var MuggleEntityIdSchema = z.string().uuid();
|
|
2883
2972
|
var LocalExecutionContextInputSchema = z.object({
|
|
2884
2973
|
originalUrl: z.string().url().describe("Original local URL used during local execution (typically localhost)"),
|
|
@@ -3251,7 +3340,8 @@ var ApiKeyRevokeInputSchema = z.object({
|
|
|
3251
3340
|
});
|
|
3252
3341
|
var AuthLoginInputSchema = z.object({
|
|
3253
3342
|
waitForCompletion: z.boolean().optional().describe("Whether to wait for browser login completion before returning. Default: true"),
|
|
3254
|
-
timeoutMs: z.number().int().positive().min(1e3).max(9e5).optional().describe("Maximum time to wait for login completion in milliseconds. Default: 120000")
|
|
3343
|
+
timeoutMs: z.number().int().positive().min(1e3).max(9e5).optional().describe("Maximum time to wait for login completion in milliseconds. Default: 120000"),
|
|
3344
|
+
forceNewSession: z.boolean().optional().describe("Force a fresh login by clearing any existing Auth0 browser session before redirecting to the device activation page. Use this to switch accounts. Default: false")
|
|
3255
3345
|
});
|
|
3256
3346
|
var AuthPollInputSchema = z.object({
|
|
3257
3347
|
deviceCode: z.string().optional().describe("Device code from the login response. Optional if a login was recently started.")
|
|
@@ -3363,17 +3453,17 @@ var PromptServiceClient = class {
|
|
|
3363
3453
|
* @param path - Path to validate.
|
|
3364
3454
|
* @throws GatewayError if path is not allowed.
|
|
3365
3455
|
*/
|
|
3366
|
-
validatePath(
|
|
3367
|
-
const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) =>
|
|
3456
|
+
validatePath(path10) {
|
|
3457
|
+
const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) => path10.startsWith(prefix));
|
|
3368
3458
|
if (!isAllowed) {
|
|
3369
|
-
const
|
|
3370
|
-
|
|
3371
|
-
path:
|
|
3459
|
+
const logger6 = getLogger();
|
|
3460
|
+
logger6.error("Path not in allowlist", {
|
|
3461
|
+
path: path10,
|
|
3372
3462
|
allowedPrefixes: ALLOWED_UPSTREAM_PREFIXES
|
|
3373
3463
|
});
|
|
3374
3464
|
throw new GatewayError({
|
|
3375
3465
|
code: "FORBIDDEN" /* FORBIDDEN */,
|
|
3376
|
-
message: `Path '${
|
|
3466
|
+
message: `Path '${path10}' is not allowed`
|
|
3377
3467
|
});
|
|
3378
3468
|
}
|
|
3379
3469
|
}
|
|
@@ -3482,7 +3572,7 @@ var PromptServiceClient = class {
|
|
|
3482
3572
|
* @throws GatewayError on validation or upstream errors.
|
|
3483
3573
|
*/
|
|
3484
3574
|
async execute(call, credentials, correlationId) {
|
|
3485
|
-
const
|
|
3575
|
+
const logger6 = getLogger();
|
|
3486
3576
|
if (!credentials.bearerToken && !credentials.apiKey) {
|
|
3487
3577
|
throw new GatewayError({
|
|
3488
3578
|
code: "UNAUTHORIZED" /* UNAUTHORIZED */,
|
|
@@ -3494,7 +3584,7 @@ var PromptServiceClient = class {
|
|
|
3494
3584
|
const headers = this.buildHeaders(credentials, correlationId);
|
|
3495
3585
|
const timeout = call.timeoutMs || this.requestTimeoutMs;
|
|
3496
3586
|
const startTime = Date.now();
|
|
3497
|
-
|
|
3587
|
+
logger6.info("Upstream request", {
|
|
3498
3588
|
correlationId,
|
|
3499
3589
|
method: call.method,
|
|
3500
3590
|
path: call.path,
|
|
@@ -3521,7 +3611,7 @@ var PromptServiceClient = class {
|
|
|
3521
3611
|
}
|
|
3522
3612
|
const response = await this.httpClient.request(requestConfig);
|
|
3523
3613
|
const latency = Date.now() - startTime;
|
|
3524
|
-
|
|
3614
|
+
logger6.info("Upstream response", {
|
|
3525
3615
|
correlationId,
|
|
3526
3616
|
statusCode: response.status,
|
|
3527
3617
|
latencyMs: latency
|
|
@@ -3552,7 +3642,7 @@ var PromptServiceClient = class {
|
|
|
3552
3642
|
throw error;
|
|
3553
3643
|
}
|
|
3554
3644
|
if (error instanceof AxiosError) {
|
|
3555
|
-
|
|
3645
|
+
logger6.error("Upstream request failed", {
|
|
3556
3646
|
correlationId,
|
|
3557
3647
|
error: error.message,
|
|
3558
3648
|
code: error.code,
|
|
@@ -3571,7 +3661,7 @@ var PromptServiceClient = class {
|
|
|
3571
3661
|
details: { upstreamPath: call.path }
|
|
3572
3662
|
});
|
|
3573
3663
|
}
|
|
3574
|
-
|
|
3664
|
+
logger6.error("Unknown upstream error", {
|
|
3575
3665
|
correlationId,
|
|
3576
3666
|
error: String(error),
|
|
3577
3667
|
latencyMs: latency
|
|
@@ -4880,7 +4970,9 @@ var authTools = [
|
|
|
4880
4970
|
localHandler: async (input) => {
|
|
4881
4971
|
const data = input;
|
|
4882
4972
|
const authService = getAuthService();
|
|
4883
|
-
const deviceCodeResponse = await authService.startDeviceCodeFlow(
|
|
4973
|
+
const deviceCodeResponse = await authService.startDeviceCodeFlow({
|
|
4974
|
+
forceNewSession: data.forceNewSession
|
|
4975
|
+
});
|
|
4884
4976
|
const waitForCompletion = data.waitForCompletion ?? true;
|
|
4885
4977
|
if (!waitForCompletion) {
|
|
4886
4978
|
return {
|
|
@@ -4985,7 +5077,7 @@ function defaultResponseMapper(response) {
|
|
|
4985
5077
|
return response.data;
|
|
4986
5078
|
}
|
|
4987
5079
|
async function executeQaTool(toolName, input, correlationId) {
|
|
4988
|
-
const
|
|
5080
|
+
const logger6 = createChildLogger(correlationId);
|
|
4989
5081
|
const tool = getQaToolByName(toolName);
|
|
4990
5082
|
if (!tool) {
|
|
4991
5083
|
return {
|
|
@@ -5026,7 +5118,7 @@ async function executeQaTool(toolName, input, correlationId) {
|
|
|
5026
5118
|
}
|
|
5027
5119
|
} catch (error) {
|
|
5028
5120
|
if (error instanceof GatewayError) {
|
|
5029
|
-
|
|
5121
|
+
logger6.warn("Tool call failed with gateway error", {
|
|
5030
5122
|
tool: toolName,
|
|
5031
5123
|
code: error.code,
|
|
5032
5124
|
message: error.message
|
|
@@ -5037,7 +5129,7 @@ async function executeQaTool(toolName, input, correlationId) {
|
|
|
5037
5129
|
};
|
|
5038
5130
|
}
|
|
5039
5131
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5040
|
-
|
|
5132
|
+
logger6.error("Tool call failed", { tool: toolName, error: errorMessage });
|
|
5041
5133
|
return {
|
|
5042
5134
|
content: JSON.stringify({ error: "INTERNAL_ERROR", message: errorMessage }),
|
|
5043
5135
|
isError: true
|
|
@@ -5045,82 +5137,12 @@ async function executeQaTool(toolName, input, correlationId) {
|
|
|
5045
5137
|
}
|
|
5046
5138
|
}
|
|
5047
5139
|
|
|
5048
|
-
// packages/mcps/src/mcp/tools/
|
|
5049
|
-
var
|
|
5050
|
-
__export(
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
});
|
|
5055
|
-
|
|
5056
|
-
// packages/mcps/src/mcp/e2e/index.ts
|
|
5057
|
-
function getQaTools() {
|
|
5058
|
-
return allQaToolDefinitions.map((tool) => ({
|
|
5059
|
-
name: tool.name,
|
|
5060
|
-
description: tool.description,
|
|
5061
|
-
inputSchema: tool.inputSchema,
|
|
5062
|
-
requiresAuth: tool.requiresAuth !== false,
|
|
5063
|
-
execute: async (params) => {
|
|
5064
|
-
return executeQaTool(tool.name, params.input, params.correlationId);
|
|
5065
|
-
}
|
|
5066
|
-
}));
|
|
5067
|
-
}
|
|
5068
|
-
|
|
5069
|
-
// packages/mcps/src/mcp/local/index.ts
|
|
5070
|
-
var local_exports2 = {};
|
|
5071
|
-
__export(local_exports2, {
|
|
5072
|
-
AuthLoginInputSchema: () => AuthLoginInputSchema2,
|
|
5073
|
-
AuthPollInputSchema: () => AuthPollInputSchema2,
|
|
5074
|
-
AuthService: () => AuthService,
|
|
5075
|
-
CancelExecutionInputSchema: () => CancelExecutionInputSchema,
|
|
5076
|
-
CleanupSessionsInputSchema: () => CleanupSessionsInputSchema,
|
|
5077
|
-
CloudMappingEntityType: () => CloudMappingEntityType,
|
|
5078
|
-
DeviceCodePollStatus: () => DeviceCodePollStatus,
|
|
5079
|
-
EmptyInputSchema: () => EmptyInputSchema2,
|
|
5080
|
-
ExecuteReplayInputSchema: () => ExecuteReplayInputSchema,
|
|
5081
|
-
ExecuteTestGenerationInputSchema: () => ExecuteTestGenerationInputSchema,
|
|
5082
|
-
ExecutionStatus: () => ExecutionStatus,
|
|
5083
|
-
HealthStatus: () => HealthStatus,
|
|
5084
|
-
ListSessionsInputSchema: () => ListSessionsInputSchema,
|
|
5085
|
-
LocalRunStatus: () => LocalRunStatus,
|
|
5086
|
-
LocalRunType: () => LocalRunType,
|
|
5087
|
-
LocalTestScriptStatus: () => LocalTestScriptStatus,
|
|
5088
|
-
LocalWorkflowFileEntityType: () => LocalWorkflowFileEntityType,
|
|
5089
|
-
LocalWorkflowRunStatus: () => LocalWorkflowRunStatus,
|
|
5090
|
-
MuggleEntityIdSchema: () => MuggleEntityIdSchema,
|
|
5091
|
-
PublishTestScriptInputSchema: () => PublishTestScriptInputSchema,
|
|
5092
|
-
RunResultGetInputSchema: () => RunResultGetInputSchema,
|
|
5093
|
-
RunResultListInputSchema: () => RunResultListInputSchema,
|
|
5094
|
-
RunResultStorageService: () => RunResultStorageService,
|
|
5095
|
-
SessionStatus: () => SessionStatus,
|
|
5096
|
-
StorageService: () => StorageService,
|
|
5097
|
-
TestCaseDetailsSchema: () => TestCaseDetailsSchema,
|
|
5098
|
-
TestResultStatus: () => TestResultStatus,
|
|
5099
|
-
TestScriptDetailsSchema: () => TestScriptDetailsSchema,
|
|
5100
|
-
TestScriptGetInputSchema: () => TestScriptGetInputSchema2,
|
|
5101
|
-
TestScriptListInputSchema: () => TestScriptListInputSchema2,
|
|
5102
|
-
allLocalQaTools: () => allLocalQaTools,
|
|
5103
|
-
cancelExecution: () => cancelExecution,
|
|
5104
|
-
executeReplay: () => executeReplay,
|
|
5105
|
-
executeTestGeneration: () => executeTestGeneration,
|
|
5106
|
-
executeTool: () => executeTool,
|
|
5107
|
-
getAuthService: () => getAuthService,
|
|
5108
|
-
getLocalQaTools: () => getLocalQaTools,
|
|
5109
|
-
getRunResultStorageService: () => getRunResultStorageService,
|
|
5110
|
-
getStorageService: () => getStorageService,
|
|
5111
|
-
getTool: () => getTool,
|
|
5112
|
-
listActiveExecutions: () => listActiveExecutions,
|
|
5113
|
-
resetAuthService: () => resetAuthService,
|
|
5114
|
-
resetRunResultStorageService: () => resetRunResultStorageService,
|
|
5115
|
-
resetStorageService: () => resetStorageService
|
|
5116
|
-
});
|
|
5117
|
-
|
|
5118
|
-
// packages/mcps/src/mcp/tools/local/index.ts
|
|
5119
|
-
var local_exports = {};
|
|
5120
|
-
__export(local_exports, {
|
|
5121
|
-
allLocalQaTools: () => allLocalQaTools,
|
|
5122
|
-
executeTool: () => executeTool,
|
|
5123
|
-
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
|
|
5124
5146
|
});
|
|
5125
5147
|
var AuthLoginInputSchema2 = z.object({
|
|
5126
5148
|
waitForCompletion: z.boolean().optional().describe("Whether to wait for browser login completion before returning. Default: true"),
|
|
@@ -5173,12 +5195,10 @@ var ExecuteTestGenerationInputSchema = z.object({
|
|
|
5173
5195
|
testCase: TestCaseDetailsSchema.describe("Test case details obtained from muggle-remote-test-case-get"),
|
|
5174
5196
|
/** Local URL to test against. */
|
|
5175
5197
|
localUrl: z.string().url().describe("Local URL to test against (e.g., http://localhost:3000)"),
|
|
5176
|
-
/** Explicit approval to launch electron-app. */
|
|
5177
|
-
approveElectronAppLaunch: z.boolean().describe("Set to true after the user explicitly approves launching electron-app"),
|
|
5178
5198
|
/** Optional timeout. */
|
|
5179
5199
|
timeoutMs: z.number().int().positive().optional().describe("Timeout in milliseconds (default: 300000 = 5 min)"),
|
|
5180
|
-
/** Show the electron-app UI during execution.
|
|
5181
|
-
showUi: z.boolean().optional().describe("Show the electron-app UI during generation.
|
|
5200
|
+
/** Show the electron-app UI during execution. Default: visible window. Pass false to run headless. */
|
|
5201
|
+
showUi: z.boolean().optional().describe("Show the electron-app UI during generation. Defaults to visible; pass false to run headless.")
|
|
5182
5202
|
});
|
|
5183
5203
|
var ExecuteReplayInputSchema = z.object({
|
|
5184
5204
|
/** Test script metadata from muggle-remote-test-script-get. */
|
|
@@ -5187,12 +5207,10 @@ var ExecuteReplayInputSchema = z.object({
|
|
|
5187
5207
|
actionScript: z.array(z.unknown()).describe("Action script steps from muggle-remote-action-script-get"),
|
|
5188
5208
|
/** Local URL to test against. */
|
|
5189
5209
|
localUrl: z.string().url().describe("Local URL to test against (e.g., http://localhost:3000)"),
|
|
5190
|
-
/** Explicit approval to launch electron-app. */
|
|
5191
|
-
approveElectronAppLaunch: z.boolean().describe("Set to true after the user explicitly approves launching electron-app"),
|
|
5192
5210
|
/** Optional timeout. */
|
|
5193
5211
|
timeoutMs: z.number().int().positive().optional().describe("Timeout in milliseconds (default: 180000 = 3 min)"),
|
|
5194
|
-
/** Show the electron-app UI during execution.
|
|
5195
|
-
showUi: z.boolean().optional().describe("Show the electron-app UI during replay.
|
|
5212
|
+
/** Show the electron-app UI during execution. Default: visible window. Pass false to run headless. */
|
|
5213
|
+
showUi: z.boolean().optional().describe("Show the electron-app UI during replay. Defaults to visible; pass false to run headless.")
|
|
5196
5214
|
});
|
|
5197
5215
|
var CancelExecutionInputSchema = z.object({
|
|
5198
5216
|
runId: MuggleEntityIdSchema.describe("Run ID (UUID) to cancel")
|
|
@@ -5223,12 +5241,12 @@ var CleanupSessionsInputSchema = z.object({
|
|
|
5223
5241
|
|
|
5224
5242
|
// packages/mcps/src/mcp/tools/local/tool-registry.ts
|
|
5225
5243
|
function createChildLogger2(correlationId) {
|
|
5226
|
-
const
|
|
5244
|
+
const logger6 = getLogger();
|
|
5227
5245
|
return {
|
|
5228
|
-
info: (msg, meta) =>
|
|
5229
|
-
error: (msg, meta) =>
|
|
5230
|
-
warn: (msg, meta) =>
|
|
5231
|
-
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 })
|
|
5232
5250
|
};
|
|
5233
5251
|
}
|
|
5234
5252
|
var checkStatusTool = {
|
|
@@ -5236,8 +5254,8 @@ var checkStatusTool = {
|
|
|
5236
5254
|
description: "Check the status of Muggle Test Local. This verifies the connection to web-service and shows current session information.",
|
|
5237
5255
|
inputSchema: EmptyInputSchema2,
|
|
5238
5256
|
execute: async (ctx) => {
|
|
5239
|
-
const
|
|
5240
|
-
|
|
5257
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5258
|
+
logger6.info("Executing muggle-local-check-status");
|
|
5241
5259
|
const authService = getAuthService();
|
|
5242
5260
|
const storageService = getStorageService();
|
|
5243
5261
|
const authStatus = authService.getAuthStatus();
|
|
@@ -5260,8 +5278,8 @@ var listSessionsTool = {
|
|
|
5260
5278
|
description: "List all stored testing sessions. Shows session IDs, status, and metadata for each session.",
|
|
5261
5279
|
inputSchema: ListSessionsInputSchema,
|
|
5262
5280
|
execute: async (ctx) => {
|
|
5263
|
-
const
|
|
5264
|
-
|
|
5281
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5282
|
+
logger6.info("Executing muggle-local-list-sessions");
|
|
5265
5283
|
const input = ListSessionsInputSchema.parse(ctx.input);
|
|
5266
5284
|
const storageService = getStorageService();
|
|
5267
5285
|
const sessions = storageService.listSessionsWithMetadata();
|
|
@@ -5288,8 +5306,8 @@ var runResultListTool = {
|
|
|
5288
5306
|
description: "List run results (test generation and replay history), optionally filtered by cloud test case ID.",
|
|
5289
5307
|
inputSchema: RunResultListInputSchema,
|
|
5290
5308
|
execute: async (ctx) => {
|
|
5291
|
-
const
|
|
5292
|
-
|
|
5309
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5310
|
+
logger6.info("Executing muggle-local-run-result-list");
|
|
5293
5311
|
const input = RunResultListInputSchema.parse(ctx.input);
|
|
5294
5312
|
const storage = getRunResultStorageService();
|
|
5295
5313
|
let results = storage.listRunResults();
|
|
@@ -5313,8 +5331,8 @@ var runResultGetTool = {
|
|
|
5313
5331
|
description: "Get detailed information about a run result including screenshots and action script output.",
|
|
5314
5332
|
inputSchema: RunResultGetInputSchema,
|
|
5315
5333
|
execute: async (ctx) => {
|
|
5316
|
-
const
|
|
5317
|
-
|
|
5334
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5335
|
+
logger6.info("Executing muggle-local-run-result-get");
|
|
5318
5336
|
const input = RunResultGetInputSchema.parse(ctx.input);
|
|
5319
5337
|
const storage = getRunResultStorageService();
|
|
5320
5338
|
const result = storage.getRunResult(input.runId);
|
|
@@ -5336,7 +5354,7 @@ var runResultGetTool = {
|
|
|
5336
5354
|
const testScript = storage.getTestScript(result.testScriptId);
|
|
5337
5355
|
testScriptSteps = testScript?.actionScript?.length;
|
|
5338
5356
|
}
|
|
5339
|
-
if (result.artifactsDir &&
|
|
5357
|
+
if (result.artifactsDir && fs5.existsSync(result.artifactsDir)) {
|
|
5340
5358
|
contentParts.push(
|
|
5341
5359
|
"",
|
|
5342
5360
|
"### Artifacts (view action script + screenshots)",
|
|
@@ -5350,20 +5368,20 @@ var runResultGetTool = {
|
|
|
5350
5368
|
const stdoutLogPath = path2.join(result.artifactsDir, "stdout.log");
|
|
5351
5369
|
const stderrLogPath = path2.join(result.artifactsDir, "stderr.log");
|
|
5352
5370
|
const artifactItems = [];
|
|
5353
|
-
if (
|
|
5371
|
+
if (fs5.existsSync(actionScriptPath)) {
|
|
5354
5372
|
artifactItems.push("- `action-script.json` \u2014 generated test steps");
|
|
5355
5373
|
}
|
|
5356
|
-
if (
|
|
5374
|
+
if (fs5.existsSync(resultsMdPath)) {
|
|
5357
5375
|
artifactItems.push("- `results.md` \u2014 step-by-step report with screenshot links");
|
|
5358
5376
|
}
|
|
5359
|
-
if (
|
|
5360
|
-
const screenshots =
|
|
5377
|
+
if (fs5.existsSync(screenshotsDir)) {
|
|
5378
|
+
const screenshots = fs5.readdirSync(screenshotsDir).filter((f) => /\.(png|jpg|jpeg|webp)$/i.test(f));
|
|
5361
5379
|
artifactItems.push(`- \`screenshots/\` \u2014 ${screenshots.length} image(s)`);
|
|
5362
5380
|
}
|
|
5363
|
-
if (
|
|
5381
|
+
if (fs5.existsSync(stdoutLogPath)) {
|
|
5364
5382
|
artifactItems.push("- `stdout.log` \u2014 electron-app stdout output");
|
|
5365
5383
|
}
|
|
5366
|
-
if (
|
|
5384
|
+
if (fs5.existsSync(stderrLogPath)) {
|
|
5367
5385
|
artifactItems.push("- `stderr.log` \u2014 electron-app stderr output");
|
|
5368
5386
|
}
|
|
5369
5387
|
if (artifactItems.length > 0) {
|
|
@@ -5388,8 +5406,8 @@ var testScriptListTool = {
|
|
|
5388
5406
|
description: "List locally generated test scripts, optionally filtered by cloud test case ID.",
|
|
5389
5407
|
inputSchema: TestScriptListInputSchema2,
|
|
5390
5408
|
execute: async (ctx) => {
|
|
5391
|
-
const
|
|
5392
|
-
|
|
5409
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5410
|
+
logger6.info("Executing muggle-local-test-script-list");
|
|
5393
5411
|
const input = TestScriptListInputSchema2.parse(ctx.input);
|
|
5394
5412
|
const storage = getRunResultStorageService();
|
|
5395
5413
|
let scripts = storage.listTestScripts();
|
|
@@ -5409,8 +5427,8 @@ var testScriptGetTool = {
|
|
|
5409
5427
|
description: "Get details of a locally generated test script including action script steps.",
|
|
5410
5428
|
inputSchema: TestScriptGetInputSchema2,
|
|
5411
5429
|
execute: async (ctx) => {
|
|
5412
|
-
const
|
|
5413
|
-
|
|
5430
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5431
|
+
logger6.info("Executing muggle-local-test-script-get");
|
|
5414
5432
|
const input = TestScriptGetInputSchema2.parse(ctx.input);
|
|
5415
5433
|
const storage = getRunResultStorageService();
|
|
5416
5434
|
const testScript = storage.getTestScript(input.testScriptId);
|
|
@@ -5432,38 +5450,13 @@ var testScriptGetTool = {
|
|
|
5432
5450
|
};
|
|
5433
5451
|
var executeTestGenerationTool = {
|
|
5434
5452
|
name: "muggle-local-execute-test-generation",
|
|
5435
|
-
description: "Generate an end-to-end (E2E) acceptance test script by launching a real browser against your web app. The browser navigates your app, executes the test case steps (like signing up, filling forms, clicking through flows), and produces a replayable test script with screenshots. Use this to create new browser tests for any user flow. Requires a test case (from muggle-remote-test-case-get) and a localhost URL. Launches an Electron browser \u2014
|
|
5453
|
+
description: "Generate an end-to-end (E2E) acceptance test script by launching a real browser against your web app. The browser navigates your app, executes the test case steps (like signing up, filling forms, clicking through flows), and produces a replayable test script with screenshots. Use this to create new browser tests for any user flow. Requires a test case (from muggle-remote-test-case-get) and a localhost URL. Launches an Electron browser \u2014 defaults to a visible window; pass showUi: false to run headless.",
|
|
5436
5454
|
inputSchema: ExecuteTestGenerationInputSchema,
|
|
5437
5455
|
execute: async (ctx) => {
|
|
5438
|
-
const
|
|
5439
|
-
|
|
5456
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5457
|
+
logger6.info("Executing muggle-local-execute-test-generation");
|
|
5440
5458
|
const input = ExecuteTestGenerationInputSchema.parse(ctx.input);
|
|
5441
|
-
|
|
5442
|
-
const showUiExplicit = input.showUi !== void 0;
|
|
5443
|
-
const uiMode = input.showUi === true ? "visible GUI (showUi: true)" : "headless (showUi: false or omitted)";
|
|
5444
|
-
return {
|
|
5445
|
-
content: [
|
|
5446
|
-
"## Electron App Launch Required",
|
|
5447
|
-
"",
|
|
5448
|
-
"This tool will launch the electron-app to generate a test script.",
|
|
5449
|
-
"Please set `approveElectronAppLaunch: true` to proceed.",
|
|
5450
|
-
"",
|
|
5451
|
-
"**Visible GUI:** Ask the user whether they want to watch the Electron window during generation.",
|
|
5452
|
-
"- If **yes** \u2192 when approving, pass `showUi: true`.",
|
|
5453
|
-
"- If **no** \u2192 when approving, pass `showUi: false` (or omit `showUi`; generation runs headless).",
|
|
5454
|
-
"",
|
|
5455
|
-
showUiExplicit ? `**Current choice:** ${uiMode}` : "**Current choice:** not set \u2014 default on approval is headless unless you pass `showUi: true`.",
|
|
5456
|
-
"",
|
|
5457
|
-
`**Test Case:** ${input.testCase.title}`,
|
|
5458
|
-
`**Local URL:** ${input.localUrl}`,
|
|
5459
|
-
"",
|
|
5460
|
-
"**Note:** The electron-app will navigate your test URL and record steps."
|
|
5461
|
-
].join("\n"),
|
|
5462
|
-
isError: false,
|
|
5463
|
-
data: { requiresApproval: true }
|
|
5464
|
-
};
|
|
5465
|
-
}
|
|
5466
|
-
const showUi = input.showUi === true;
|
|
5459
|
+
const showUi = input.showUi !== false;
|
|
5467
5460
|
try {
|
|
5468
5461
|
const result = await executeTestGeneration({
|
|
5469
5462
|
testCase: input.testCase,
|
|
@@ -5488,46 +5481,20 @@ var executeTestGenerationTool = {
|
|
|
5488
5481
|
};
|
|
5489
5482
|
} catch (error) {
|
|
5490
5483
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5491
|
-
|
|
5484
|
+
logger6.error("Test generation failed", { error: errorMessage });
|
|
5492
5485
|
return { content: `Test generation failed: ${errorMessage}`, isError: true };
|
|
5493
5486
|
}
|
|
5494
5487
|
}
|
|
5495
5488
|
};
|
|
5496
5489
|
var executeReplayTool = {
|
|
5497
5490
|
name: "muggle-local-execute-replay",
|
|
5498
|
-
description: "Replay an existing E2E acceptance test script in a real browser to verify your app still works correctly \u2014 use this for regression testing after code changes. The browser executes each saved step and captures screenshots so you can see what happened. Requires: (1) test script metadata from muggle-remote-test-script-get, (2) actionScript content from muggle-remote-action-script-get using the testScript.actionScriptId, and (3) a localhost URL. Launches an Electron browser \u2014
|
|
5491
|
+
description: "Replay an existing E2E acceptance test script in a real browser to verify your app still works correctly \u2014 use this for regression testing after code changes. The browser executes each saved step and captures screenshots so you can see what happened. Requires: (1) test script metadata from muggle-remote-test-script-get, (2) actionScript content from muggle-remote-action-script-get using the testScript.actionScriptId, and (3) a localhost URL. Launches an Electron browser \u2014 defaults to a visible window; pass showUi: false to run headless.",
|
|
5499
5492
|
inputSchema: ExecuteReplayInputSchema,
|
|
5500
5493
|
execute: async (ctx) => {
|
|
5501
|
-
const
|
|
5502
|
-
|
|
5494
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5495
|
+
logger6.info("Executing muggle-local-execute-replay");
|
|
5503
5496
|
const input = ExecuteReplayInputSchema.parse(ctx.input);
|
|
5504
|
-
|
|
5505
|
-
const showUiExplicit = input.showUi !== void 0;
|
|
5506
|
-
const uiMode = input.showUi === true ? "visible GUI (showUi: true)" : "headless (showUi: false or omitted)";
|
|
5507
|
-
return {
|
|
5508
|
-
content: [
|
|
5509
|
-
"## Electron App Launch Required",
|
|
5510
|
-
"",
|
|
5511
|
-
"This tool will launch the electron-app to replay a test script.",
|
|
5512
|
-
"Please set `approveElectronAppLaunch: true` to proceed.",
|
|
5513
|
-
"",
|
|
5514
|
-
"**Visible GUI:** Ask the user whether they want to watch the Electron window during replay.",
|
|
5515
|
-
"- If **yes** \u2192 when approving, pass `showUi: true`.",
|
|
5516
|
-
"- If **no** \u2192 when approving, pass `showUi: false` (or omit `showUi`; replay runs headless).",
|
|
5517
|
-
"",
|
|
5518
|
-
showUiExplicit ? `**Current choice:** ${uiMode}` : "**Current choice:** not set \u2014 default on approval is headless unless you pass `showUi: true`.",
|
|
5519
|
-
"",
|
|
5520
|
-
`**Test Script:** ${input.testScript.name}`,
|
|
5521
|
-
`**Local URL:** ${input.localUrl}`,
|
|
5522
|
-
`**Steps:** ${input.actionScript.length}`,
|
|
5523
|
-
"",
|
|
5524
|
-
"**Note:** The electron-app will execute the test steps against your local URL."
|
|
5525
|
-
].join("\n"),
|
|
5526
|
-
isError: false,
|
|
5527
|
-
data: { requiresApproval: true }
|
|
5528
|
-
};
|
|
5529
|
-
}
|
|
5530
|
-
const showUi = input.showUi === true;
|
|
5497
|
+
const showUi = input.showUi !== false;
|
|
5531
5498
|
try {
|
|
5532
5499
|
const result = await executeReplay({
|
|
5533
5500
|
testScript: input.testScript,
|
|
@@ -5553,7 +5520,7 @@ var executeReplayTool = {
|
|
|
5553
5520
|
};
|
|
5554
5521
|
} catch (error) {
|
|
5555
5522
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5556
|
-
|
|
5523
|
+
logger6.error("Test replay failed", { error: errorMessage });
|
|
5557
5524
|
return { content: `Test replay failed: ${errorMessage}`, isError: true };
|
|
5558
5525
|
}
|
|
5559
5526
|
}
|
|
@@ -5563,8 +5530,8 @@ var cancelExecutionTool = {
|
|
|
5563
5530
|
description: "Cancel an active test generation or replay execution.",
|
|
5564
5531
|
inputSchema: CancelExecutionInputSchema,
|
|
5565
5532
|
execute: async (ctx) => {
|
|
5566
|
-
const
|
|
5567
|
-
|
|
5533
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5534
|
+
logger6.info("Executing muggle-local-cancel-execution");
|
|
5568
5535
|
const input = CancelExecutionInputSchema.parse(ctx.input);
|
|
5569
5536
|
const cancelled = cancelExecution({ runId: input.runId });
|
|
5570
5537
|
if (cancelled) {
|
|
@@ -5578,8 +5545,8 @@ var publishTestScriptTool = {
|
|
|
5578
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.",
|
|
5579
5546
|
inputSchema: PublishTestScriptInputSchema,
|
|
5580
5547
|
execute: async (ctx) => {
|
|
5581
|
-
const
|
|
5582
|
-
|
|
5548
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5549
|
+
logger6.info("Executing muggle-local-publish-test-script");
|
|
5583
5550
|
const input = PublishTestScriptInputSchema.parse(ctx.input);
|
|
5584
5551
|
const storage = getRunResultStorageService();
|
|
5585
5552
|
const runResult = storage.getRunResult(input.runId);
|
|
@@ -5698,7 +5665,7 @@ var publishTestScriptTool = {
|
|
|
5698
5665
|
};
|
|
5699
5666
|
} catch (error) {
|
|
5700
5667
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5701
|
-
|
|
5668
|
+
logger6.error("Failed to publish local test script to cloud", {
|
|
5702
5669
|
runId: input.runId,
|
|
5703
5670
|
cloudTestCaseId: input.cloudTestCaseId,
|
|
5704
5671
|
error: errorMessage
|
|
@@ -5744,7 +5711,161 @@ async function executeTool(name, input, correlationId) {
|
|
|
5744
5711
|
return tool.execute({ input, correlationId });
|
|
5745
5712
|
}
|
|
5746
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
|
+
|
|
5747
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
|
+
});
|
|
5748
5869
|
function getLocalQaTools() {
|
|
5749
5870
|
return allLocalQaTools.map((tool) => ({
|
|
5750
5871
|
name: tool.name,
|
|
@@ -5778,26 +5899,6 @@ function isLocalOnlyTool(toolName) {
|
|
|
5778
5899
|
return localOnlyTools.includes(toolName);
|
|
5779
5900
|
}
|
|
5780
5901
|
|
|
5781
|
-
// packages/mcps/src/mcp/index.ts
|
|
5782
|
-
var mcp_exports = {};
|
|
5783
|
-
__export(mcp_exports, {
|
|
5784
|
-
agents: () => agents_exports,
|
|
5785
|
-
e2e: () => e2e_exports2,
|
|
5786
|
-
localQa: () => local_exports2,
|
|
5787
|
-
plugins: () => plugins_exports,
|
|
5788
|
-
qa: () => e2e_exports2,
|
|
5789
|
-
skills: () => skills_exports,
|
|
5790
|
-
tools: () => tools_exports
|
|
5791
|
-
});
|
|
5792
|
-
|
|
5793
|
-
// packages/mcps/src/mcp/tools/index.ts
|
|
5794
|
-
var tools_exports = {};
|
|
5795
|
-
__export(tools_exports, {
|
|
5796
|
-
e2e: () => e2e_exports,
|
|
5797
|
-
localQa: () => local_exports,
|
|
5798
|
-
qa: () => e2e_exports
|
|
5799
|
-
});
|
|
5800
|
-
|
|
5801
5902
|
// packages/mcps/src/mcp/skills/index.ts
|
|
5802
5903
|
var skills_exports = {};
|
|
5803
5904
|
|
|
@@ -5807,2164 +5908,4 @@ var plugins_exports = {};
|
|
|
5807
5908
|
// packages/mcps/src/mcp/agents/index.ts
|
|
5808
5909
|
var agents_exports = {};
|
|
5809
5910
|
|
|
5810
|
-
|
|
5811
|
-
var src_exports = {};
|
|
5812
|
-
__export(src_exports, {
|
|
5813
|
-
buildElectronAppChecksumsUrl: () => buildElectronAppChecksumsUrl,
|
|
5814
|
-
buildElectronAppReleaseAssetUrl: () => buildElectronAppReleaseAssetUrl,
|
|
5815
|
-
buildElectronAppReleaseTag: () => buildElectronAppReleaseTag,
|
|
5816
|
-
calculateFileChecksum: () => calculateFileChecksum,
|
|
5817
|
-
createApiKeyWithToken: () => createApiKeyWithToken,
|
|
5818
|
-
createChildLogger: () => createChildLogger,
|
|
5819
|
-
deleteApiKeyData: () => deleteApiKeyData,
|
|
5820
|
-
deleteCredentials: () => deleteCredentials,
|
|
5821
|
-
e2e: () => e2e_exports2,
|
|
5822
|
-
getApiKey: () => getApiKey,
|
|
5823
|
-
getApiKeyFilePath: () => getApiKeyFilePath,
|
|
5824
|
-
getAuthService: () => getAuthService,
|
|
5825
|
-
getBundledElectronAppVersion: () => getBundledElectronAppVersion,
|
|
5826
|
-
getCallerCredentials: () => getCallerCredentials,
|
|
5827
|
-
getCallerCredentialsAsync: () => getCallerCredentialsAsync,
|
|
5828
|
-
getChecksumForPlatform: () => getChecksumForPlatform,
|
|
5829
|
-
getConfig: () => getConfig,
|
|
5830
|
-
getCredentialsFilePath: () => getCredentialsFilePath,
|
|
5831
|
-
getDataDir: () => getDataDir2,
|
|
5832
|
-
getDownloadBaseUrl: () => getDownloadBaseUrl,
|
|
5833
|
-
getElectronAppChecksums: () => getElectronAppChecksums,
|
|
5834
|
-
getElectronAppDir: () => getElectronAppDir,
|
|
5835
|
-
getElectronAppVersion: () => getElectronAppVersion,
|
|
5836
|
-
getElectronAppVersionSource: () => getElectronAppVersionSource,
|
|
5837
|
-
getLocalQaTools: () => getLocalQaTools,
|
|
5838
|
-
getLogger: () => getLogger,
|
|
5839
|
-
getPlatformKey: () => getPlatformKey,
|
|
5840
|
-
getQaTools: () => getQaTools,
|
|
5841
|
-
getValidApiKeyData: () => getValidApiKeyData,
|
|
5842
|
-
getValidCredentials: () => getValidCredentials,
|
|
5843
|
-
hasApiKey: () => hasApiKey,
|
|
5844
|
-
isElectronAppInstalled: () => isElectronAppInstalled,
|
|
5845
|
-
loadApiKeyData: () => loadApiKeyData,
|
|
5846
|
-
loadCredentials: () => loadCredentials,
|
|
5847
|
-
localQa: () => local_exports2,
|
|
5848
|
-
mcp: () => mcp_exports,
|
|
5849
|
-
openBrowserUrl: () => openBrowserUrl,
|
|
5850
|
-
performLogin: () => performLogin,
|
|
5851
|
-
performLogout: () => performLogout,
|
|
5852
|
-
pollDeviceCode: () => pollDeviceCode,
|
|
5853
|
-
qa: () => e2e_exports2,
|
|
5854
|
-
resetConfig: () => resetConfig,
|
|
5855
|
-
resetLogger: () => resetLogger,
|
|
5856
|
-
saveApiKey: () => saveApiKey,
|
|
5857
|
-
saveApiKeyData: () => saveApiKeyData,
|
|
5858
|
-
saveCredentials: () => saveCredentials,
|
|
5859
|
-
startDeviceCodeFlow: () => startDeviceCodeFlow,
|
|
5860
|
-
toolRequiresAuth: () => toolRequiresAuth,
|
|
5861
|
-
verifyFileChecksum: () => verifyFileChecksum
|
|
5862
|
-
});
|
|
5863
|
-
var logger5 = getLogger();
|
|
5864
|
-
function getPlatformKey() {
|
|
5865
|
-
const os4 = platform();
|
|
5866
|
-
const arch3 = process.arch;
|
|
5867
|
-
switch (os4) {
|
|
5868
|
-
case "darwin":
|
|
5869
|
-
return arch3 === "arm64" ? "darwin-arm64" : "darwin-x64";
|
|
5870
|
-
case "win32":
|
|
5871
|
-
return "win32-x64";
|
|
5872
|
-
case "linux":
|
|
5873
|
-
return "linux-x64";
|
|
5874
|
-
default:
|
|
5875
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
5876
|
-
}
|
|
5877
|
-
}
|
|
5878
|
-
async function calculateFileChecksum(filePath) {
|
|
5879
|
-
return new Promise((resolve4, reject) => {
|
|
5880
|
-
const hash = crypto.createHash("sha256");
|
|
5881
|
-
const stream = fs3.createReadStream(filePath);
|
|
5882
|
-
stream.on("data", (data) => {
|
|
5883
|
-
hash.update(data);
|
|
5884
|
-
});
|
|
5885
|
-
stream.on("end", () => {
|
|
5886
|
-
resolve4(hash.digest("hex"));
|
|
5887
|
-
});
|
|
5888
|
-
stream.on("error", (error) => {
|
|
5889
|
-
reject(error);
|
|
5890
|
-
});
|
|
5891
|
-
});
|
|
5892
|
-
}
|
|
5893
|
-
async function verifyFileChecksum(filePath, expectedChecksum) {
|
|
5894
|
-
if (!expectedChecksum || expectedChecksum.trim() === "") {
|
|
5895
|
-
logger5.warn("Checksum verification skipped - no checksum provided", {
|
|
5896
|
-
file: path2.basename(filePath)
|
|
5897
|
-
});
|
|
5898
|
-
return {
|
|
5899
|
-
valid: true,
|
|
5900
|
-
expected: "",
|
|
5901
|
-
actual: "",
|
|
5902
|
-
error: "Checksum verification skipped - no checksum configured"
|
|
5903
|
-
};
|
|
5904
|
-
}
|
|
5905
|
-
try {
|
|
5906
|
-
const actualChecksum = await calculateFileChecksum(filePath);
|
|
5907
|
-
const normalizedExpected = expectedChecksum.toLowerCase().trim();
|
|
5908
|
-
const normalizedActual = actualChecksum.toLowerCase();
|
|
5909
|
-
const valid = normalizedExpected === normalizedActual;
|
|
5910
|
-
if (!valid) {
|
|
5911
|
-
logger5.error("Checksum verification failed", {
|
|
5912
|
-
file: path2.basename(filePath),
|
|
5913
|
-
expected: normalizedExpected,
|
|
5914
|
-
actual: normalizedActual
|
|
5915
|
-
});
|
|
5916
|
-
} else {
|
|
5917
|
-
logger5.info("Checksum verified successfully", {
|
|
5918
|
-
file: path2.basename(filePath),
|
|
5919
|
-
checksum: normalizedActual
|
|
5920
|
-
});
|
|
5921
|
-
}
|
|
5922
|
-
return {
|
|
5923
|
-
valid,
|
|
5924
|
-
expected: normalizedExpected,
|
|
5925
|
-
actual: normalizedActual,
|
|
5926
|
-
error: valid ? void 0 : "Checksum mismatch - file may be corrupted or tampered with"
|
|
5927
|
-
};
|
|
5928
|
-
} catch (error) {
|
|
5929
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5930
|
-
logger5.error("Checksum calculation failed", {
|
|
5931
|
-
file: path2.basename(filePath),
|
|
5932
|
-
error: errorMessage
|
|
5933
|
-
});
|
|
5934
|
-
return {
|
|
5935
|
-
valid: false,
|
|
5936
|
-
expected: expectedChecksum,
|
|
5937
|
-
actual: "",
|
|
5938
|
-
error: `Failed to calculate checksum: ${errorMessage}`
|
|
5939
|
-
};
|
|
5940
|
-
}
|
|
5941
|
-
}
|
|
5942
|
-
function getChecksumForPlatform(checksums) {
|
|
5943
|
-
if (!checksums) {
|
|
5944
|
-
return "";
|
|
5945
|
-
}
|
|
5946
|
-
const platformKey = getPlatformKey();
|
|
5947
|
-
return checksums[platformKey] || "";
|
|
5948
|
-
}
|
|
5949
|
-
var registeredTools = [];
|
|
5950
|
-
function registerTools(tools) {
|
|
5951
|
-
registeredTools = [...registeredTools, ...tools];
|
|
5952
|
-
}
|
|
5953
|
-
function getAllTools() {
|
|
5954
|
-
return registeredTools;
|
|
5955
|
-
}
|
|
5956
|
-
function clearTools() {
|
|
5957
|
-
registeredTools = [];
|
|
5958
|
-
}
|
|
5959
|
-
function zodToJsonSchema(schema) {
|
|
5960
|
-
try {
|
|
5961
|
-
if (schema && typeof schema === "object" && "safeParse" in schema) {
|
|
5962
|
-
return z.toJSONSchema(schema);
|
|
5963
|
-
}
|
|
5964
|
-
} catch {
|
|
5965
|
-
}
|
|
5966
|
-
try {
|
|
5967
|
-
const zodSchema = schema;
|
|
5968
|
-
if (zodSchema._def) {
|
|
5969
|
-
return convertZodDef(zodSchema);
|
|
5970
|
-
}
|
|
5971
|
-
} catch {
|
|
5972
|
-
}
|
|
5973
|
-
return {
|
|
5974
|
-
type: "object",
|
|
5975
|
-
properties: {},
|
|
5976
|
-
additionalProperties: true
|
|
5977
|
-
};
|
|
5978
|
-
}
|
|
5979
|
-
function convertZodDef(schema) {
|
|
5980
|
-
const zodSchema = schema;
|
|
5981
|
-
if (!zodSchema._def) {
|
|
5982
|
-
return { type: "object" };
|
|
5983
|
-
}
|
|
5984
|
-
const def = zodSchema._def;
|
|
5985
|
-
const typeName = def.typeName ?? def.type;
|
|
5986
|
-
switch (typeName) {
|
|
5987
|
-
case "ZodObject":
|
|
5988
|
-
case "object": {
|
|
5989
|
-
const shapeFromDef = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
5990
|
-
const shape = shapeFromDef || zodSchema.shape || {};
|
|
5991
|
-
const properties = {};
|
|
5992
|
-
const required = [];
|
|
5993
|
-
for (const [key, value] of Object.entries(shape)) {
|
|
5994
|
-
properties[key] = convertZodDef(value);
|
|
5995
|
-
const valueDef = value._def;
|
|
5996
|
-
if (valueDef?.typeName !== "ZodOptional") {
|
|
5997
|
-
required.push(key);
|
|
5998
|
-
}
|
|
5999
|
-
}
|
|
6000
|
-
const result = {
|
|
6001
|
-
type: "object",
|
|
6002
|
-
properties
|
|
6003
|
-
};
|
|
6004
|
-
if (required.length > 0) {
|
|
6005
|
-
result.required = required;
|
|
6006
|
-
}
|
|
6007
|
-
return result;
|
|
6008
|
-
}
|
|
6009
|
-
case "ZodString":
|
|
6010
|
-
case "string": {
|
|
6011
|
-
const result = { type: "string" };
|
|
6012
|
-
if (def.description) result.description = def.description;
|
|
6013
|
-
if (def.checks) {
|
|
6014
|
-
for (const check of def.checks) {
|
|
6015
|
-
if (check.kind === "min") result.minLength = check.value;
|
|
6016
|
-
if (check.kind === "max") result.maxLength = check.value;
|
|
6017
|
-
if (check.kind === "url") result.format = "uri";
|
|
6018
|
-
if (check.kind === "email") result.format = "email";
|
|
6019
|
-
}
|
|
6020
|
-
}
|
|
6021
|
-
return result;
|
|
6022
|
-
}
|
|
6023
|
-
case "ZodNumber":
|
|
6024
|
-
case "number": {
|
|
6025
|
-
const result = { type: "number" };
|
|
6026
|
-
if (def.description) result.description = def.description;
|
|
6027
|
-
if (def.checks) {
|
|
6028
|
-
for (const check of def.checks) {
|
|
6029
|
-
if (check.kind === "int") result.type = "integer";
|
|
6030
|
-
if (check.kind === "min") result.minimum = check.value;
|
|
6031
|
-
if (check.kind === "max") result.maximum = check.value;
|
|
6032
|
-
}
|
|
6033
|
-
}
|
|
6034
|
-
return result;
|
|
6035
|
-
}
|
|
6036
|
-
case "ZodBoolean":
|
|
6037
|
-
case "boolean": {
|
|
6038
|
-
const result = { type: "boolean" };
|
|
6039
|
-
if (def.description) result.description = def.description;
|
|
6040
|
-
return result;
|
|
6041
|
-
}
|
|
6042
|
-
case "ZodArray":
|
|
6043
|
-
case "array": {
|
|
6044
|
-
const result = {
|
|
6045
|
-
type: "array",
|
|
6046
|
-
items: def.innerType ? convertZodDef(def.innerType) : def.element ? convertZodDef(def.element) : {}
|
|
6047
|
-
};
|
|
6048
|
-
if (def.description) result.description = def.description;
|
|
6049
|
-
return result;
|
|
6050
|
-
}
|
|
6051
|
-
case "ZodEnum":
|
|
6052
|
-
case "enum": {
|
|
6053
|
-
const enumValues = Array.isArray(def.values) ? def.values : def.values ? Object.values(def.values) : [];
|
|
6054
|
-
const result = {
|
|
6055
|
-
type: "string",
|
|
6056
|
-
enum: enumValues
|
|
6057
|
-
};
|
|
6058
|
-
if (def.description) result.description = def.description;
|
|
6059
|
-
return result;
|
|
6060
|
-
}
|
|
6061
|
-
case "ZodOptional":
|
|
6062
|
-
case "optional": {
|
|
6063
|
-
const inner = def.innerType ? convertZodDef(def.innerType) : {};
|
|
6064
|
-
if (def.description) inner.description = def.description;
|
|
6065
|
-
return inner;
|
|
6066
|
-
}
|
|
6067
|
-
case "ZodUnion": {
|
|
6068
|
-
const options = def.options || [];
|
|
6069
|
-
return {
|
|
6070
|
-
oneOf: options.map((opt) => convertZodDef(opt))
|
|
6071
|
-
};
|
|
6072
|
-
}
|
|
6073
|
-
default:
|
|
6074
|
-
return { type: "object" };
|
|
6075
|
-
}
|
|
6076
|
-
}
|
|
6077
|
-
async function handleJitAuth(toolName, correlationId) {
|
|
6078
|
-
const childLogger = createChildLogger(correlationId);
|
|
6079
|
-
if (!toolRequiresAuth(toolName)) {
|
|
6080
|
-
return { credentials: {}, authTriggered: false };
|
|
6081
|
-
}
|
|
6082
|
-
const credentials = getCallerCredentials();
|
|
6083
|
-
if (credentials.apiKey || credentials.bearerToken) {
|
|
6084
|
-
return { credentials, authTriggered: false };
|
|
6085
|
-
}
|
|
6086
|
-
childLogger.info("No credentials found, triggering JIT auth", { tool: toolName });
|
|
6087
|
-
return { credentials: {}, authTriggered: false };
|
|
6088
|
-
}
|
|
6089
|
-
function createUnifiedMcpServer(options) {
|
|
6090
|
-
const logger14 = getLogger();
|
|
6091
|
-
const config = getConfig();
|
|
6092
|
-
const server = new Server(
|
|
6093
|
-
{
|
|
6094
|
-
name: config.serverName,
|
|
6095
|
-
version: config.serverVersion
|
|
6096
|
-
},
|
|
6097
|
-
{
|
|
6098
|
-
capabilities: {
|
|
6099
|
-
tools: {},
|
|
6100
|
-
resources: {}
|
|
6101
|
-
},
|
|
6102
|
-
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."
|
|
6103
|
-
}
|
|
6104
|
-
);
|
|
6105
|
-
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
6106
|
-
const tools = getAllTools();
|
|
6107
|
-
logger14.debug("Listing tools", { count: tools.length });
|
|
6108
|
-
const toolDefinitions = tools.map((tool) => {
|
|
6109
|
-
const jsonSchema = zodToJsonSchema(tool.inputSchema);
|
|
6110
|
-
return {
|
|
6111
|
-
name: tool.name,
|
|
6112
|
-
description: tool.description,
|
|
6113
|
-
inputSchema: jsonSchema
|
|
6114
|
-
};
|
|
6115
|
-
});
|
|
6116
|
-
return { tools: toolDefinitions };
|
|
6117
|
-
});
|
|
6118
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
6119
|
-
const correlationId = v4();
|
|
6120
|
-
const childLogger = createChildLogger(correlationId);
|
|
6121
|
-
const toolName = request.params.name;
|
|
6122
|
-
const toolInput = request.params.arguments || {};
|
|
6123
|
-
childLogger.info("Tool call received", {
|
|
6124
|
-
tool: toolName,
|
|
6125
|
-
hasArguments: Object.keys(toolInput).length > 0
|
|
6126
|
-
});
|
|
6127
|
-
try {
|
|
6128
|
-
const tool = getAllTools().find((t) => t.name === toolName);
|
|
6129
|
-
if (!tool) {
|
|
6130
|
-
return {
|
|
6131
|
-
content: [
|
|
6132
|
-
{
|
|
6133
|
-
type: "text",
|
|
6134
|
-
text: JSON.stringify({
|
|
6135
|
-
error: "NOT_FOUND",
|
|
6136
|
-
message: `Unknown tool: ${toolName}`
|
|
6137
|
-
})
|
|
6138
|
-
}
|
|
6139
|
-
],
|
|
6140
|
-
isError: true
|
|
6141
|
-
};
|
|
6142
|
-
}
|
|
6143
|
-
const { authTriggered } = await handleJitAuth(toolName, correlationId);
|
|
6144
|
-
if (authTriggered) {
|
|
6145
|
-
return {
|
|
6146
|
-
content: [
|
|
6147
|
-
{
|
|
6148
|
-
type: "text",
|
|
6149
|
-
text: JSON.stringify({
|
|
6150
|
-
message: "Authentication required. Please complete login in your browser."
|
|
6151
|
-
})
|
|
6152
|
-
}
|
|
6153
|
-
]
|
|
6154
|
-
};
|
|
6155
|
-
}
|
|
6156
|
-
const startTime = Date.now();
|
|
6157
|
-
const result = await tool.execute({
|
|
6158
|
-
input: toolInput,
|
|
6159
|
-
correlationId
|
|
6160
|
-
});
|
|
6161
|
-
const latency = Date.now() - startTime;
|
|
6162
|
-
childLogger.info("Tool call completed", {
|
|
6163
|
-
tool: toolName,
|
|
6164
|
-
latencyMs: latency,
|
|
6165
|
-
isError: result.isError
|
|
6166
|
-
});
|
|
6167
|
-
return {
|
|
6168
|
-
content: [
|
|
6169
|
-
{
|
|
6170
|
-
type: "text",
|
|
6171
|
-
text: result.content
|
|
6172
|
-
}
|
|
6173
|
-
],
|
|
6174
|
-
isError: result.isError
|
|
6175
|
-
};
|
|
6176
|
-
} catch (error) {
|
|
6177
|
-
if (error instanceof ZodError) {
|
|
6178
|
-
childLogger.warn("Tool call failed with validation error", {
|
|
6179
|
-
tool: toolName,
|
|
6180
|
-
errors: error.issues
|
|
6181
|
-
});
|
|
6182
|
-
const issueMessages = error.issues.slice(0, 3).map((issue) => {
|
|
6183
|
-
const path15 = issue.path.join(".");
|
|
6184
|
-
return path15 ? `'${path15}': ${issue.message}` : issue.message;
|
|
6185
|
-
});
|
|
6186
|
-
return {
|
|
6187
|
-
content: [
|
|
6188
|
-
{
|
|
6189
|
-
type: "text",
|
|
6190
|
-
text: JSON.stringify({
|
|
6191
|
-
error: "INVALID_ARGUMENT",
|
|
6192
|
-
message: `Invalid input: ${issueMessages.join("; ")}`
|
|
6193
|
-
})
|
|
6194
|
-
}
|
|
6195
|
-
],
|
|
6196
|
-
isError: true
|
|
6197
|
-
};
|
|
6198
|
-
}
|
|
6199
|
-
childLogger.error("Tool call failed with error", {
|
|
6200
|
-
tool: toolName,
|
|
6201
|
-
error: String(error)
|
|
6202
|
-
});
|
|
6203
|
-
return {
|
|
6204
|
-
content: [
|
|
6205
|
-
{
|
|
6206
|
-
type: "text",
|
|
6207
|
-
text: JSON.stringify({
|
|
6208
|
-
error: "INTERNAL_ERROR",
|
|
6209
|
-
message: error instanceof Error ? error.message : "An unexpected error occurred"
|
|
6210
|
-
})
|
|
6211
|
-
}
|
|
6212
|
-
],
|
|
6213
|
-
isError: true
|
|
6214
|
-
};
|
|
6215
|
-
}
|
|
6216
|
-
});
|
|
6217
|
-
server.setRequestHandler(ListResourcesRequestSchema, () => {
|
|
6218
|
-
logger14.debug("Listing resources");
|
|
6219
|
-
return { resources: [] };
|
|
6220
|
-
});
|
|
6221
|
-
server.setRequestHandler(ReadResourceRequestSchema, (request) => {
|
|
6222
|
-
const uri = request.params.uri;
|
|
6223
|
-
logger14.debug("Reading resource", { uri });
|
|
6224
|
-
return {
|
|
6225
|
-
contents: [
|
|
6226
|
-
{
|
|
6227
|
-
uri,
|
|
6228
|
-
mimeType: "text/plain",
|
|
6229
|
-
text: `Resource not found: ${uri}`
|
|
6230
|
-
}
|
|
6231
|
-
]
|
|
6232
|
-
};
|
|
6233
|
-
});
|
|
6234
|
-
logger14.info("Unified MCP server configured", {
|
|
6235
|
-
tools: getAllTools().length,
|
|
6236
|
-
enableQaTools: options.enableQaTools,
|
|
6237
|
-
enableLocalTools: options.enableLocalTools
|
|
6238
|
-
});
|
|
6239
|
-
return server;
|
|
6240
|
-
}
|
|
6241
|
-
|
|
6242
|
-
// src/server/index.ts
|
|
6243
|
-
var server_exports = {};
|
|
6244
|
-
__export(server_exports, {
|
|
6245
|
-
clearTools: () => clearTools,
|
|
6246
|
-
createUnifiedMcpServer: () => createUnifiedMcpServer,
|
|
6247
|
-
getAllTools: () => getAllTools,
|
|
6248
|
-
registerTools: () => registerTools,
|
|
6249
|
-
startStdioServer: () => startStdioServer
|
|
6250
|
-
});
|
|
6251
|
-
var logger6 = getLogger();
|
|
6252
|
-
async function startStdioServer(server) {
|
|
6253
|
-
logger6.info("Starting stdio server transport");
|
|
6254
|
-
const transport = new StdioServerTransport();
|
|
6255
|
-
await server.connect(transport);
|
|
6256
|
-
logger6.info("Stdio server connected");
|
|
6257
|
-
const shutdown = (signal) => {
|
|
6258
|
-
logger6.info(`Received ${signal}, shutting down...`);
|
|
6259
|
-
process.exit(0);
|
|
6260
|
-
};
|
|
6261
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
6262
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
6263
|
-
}
|
|
6264
|
-
|
|
6265
|
-
// src/cli/pr-section/selectors.ts
|
|
6266
|
-
var ONE_LINER_BUDGET = 160;
|
|
6267
|
-
function selectHero(report) {
|
|
6268
|
-
const firstFailed = report.tests.find(
|
|
6269
|
-
(t) => t.status === "failed"
|
|
6270
|
-
);
|
|
6271
|
-
if (firstFailed) {
|
|
6272
|
-
const step = firstFailed.steps.find((s) => s.stepIndex === firstFailed.failureStepIndex);
|
|
6273
|
-
if (step) {
|
|
6274
|
-
return {
|
|
6275
|
-
screenshotUrl: step.screenshotUrl,
|
|
6276
|
-
testName: firstFailed.name,
|
|
6277
|
-
kind: "failure"
|
|
6278
|
-
};
|
|
6279
|
-
}
|
|
6280
|
-
}
|
|
6281
|
-
const firstPassedWithSteps = report.tests.find(
|
|
6282
|
-
(t) => t.status === "passed" && t.steps.length > 0
|
|
6283
|
-
);
|
|
6284
|
-
if (firstPassedWithSteps) {
|
|
6285
|
-
const lastStep = firstPassedWithSteps.steps[firstPassedWithSteps.steps.length - 1];
|
|
6286
|
-
return {
|
|
6287
|
-
screenshotUrl: lastStep.screenshotUrl,
|
|
6288
|
-
testName: firstPassedWithSteps.name,
|
|
6289
|
-
kind: "final"
|
|
6290
|
-
};
|
|
6291
|
-
}
|
|
6292
|
-
return null;
|
|
6293
|
-
}
|
|
6294
|
-
function buildOneLiner(report) {
|
|
6295
|
-
const total = report.tests.length;
|
|
6296
|
-
if (total === 0) {
|
|
6297
|
-
return "No acceptance tests were executed.";
|
|
6298
|
-
}
|
|
6299
|
-
const failed = report.tests.filter((t) => t.status === "failed");
|
|
6300
|
-
if (failed.length === 0) {
|
|
6301
|
-
return `All ${total} acceptance tests passed.`;
|
|
6302
|
-
}
|
|
6303
|
-
const first = failed[0];
|
|
6304
|
-
const prefix = `${failed.length} of ${total} failed \u2014 "${first.name}" broke at step ${first.failureStepIndex}: `;
|
|
6305
|
-
const available = ONE_LINER_BUDGET - prefix.length - 1;
|
|
6306
|
-
const error = first.error.length > available ? first.error.slice(0, Math.max(0, available - 1)) + "\u2026" : first.error;
|
|
6307
|
-
return `${prefix}${error}.`;
|
|
6308
|
-
}
|
|
6309
|
-
|
|
6310
|
-
// src/cli/pr-section/render.ts
|
|
6311
|
-
var DASHBOARD_URL_BASE = "https://www.muggle-ai.com/muggleTestV0/dashboard/projects";
|
|
6312
|
-
var ROW_THUMB_WIDTH = 120;
|
|
6313
|
-
var DETAIL_THUMB_WIDTH = 200;
|
|
6314
|
-
var HERO_WIDTH = 480;
|
|
6315
|
-
function thumbnail(url, width) {
|
|
6316
|
-
return `<a href="${url}"><img src="${url}" width="${width}"></a>`;
|
|
6317
|
-
}
|
|
6318
|
-
function counts(report) {
|
|
6319
|
-
const passed = report.tests.filter((t) => t.status === "passed").length;
|
|
6320
|
-
const failed = report.tests.filter((t) => t.status === "failed").length;
|
|
6321
|
-
return { passed, failed, text: `**${passed} passed / ${failed} failed**` };
|
|
6322
|
-
}
|
|
6323
|
-
function renderSummary(report) {
|
|
6324
|
-
const { text: countsLine } = counts(report);
|
|
6325
|
-
const oneLiner = buildOneLiner(report);
|
|
6326
|
-
const hero = selectHero(report);
|
|
6327
|
-
const dashboard = `${DASHBOARD_URL_BASE}/${report.projectId}/scripts`;
|
|
6328
|
-
const lines = [
|
|
6329
|
-
countsLine,
|
|
6330
|
-
"",
|
|
6331
|
-
oneLiner,
|
|
6332
|
-
""
|
|
6333
|
-
];
|
|
6334
|
-
if (hero) {
|
|
6335
|
-
lines.push(
|
|
6336
|
-
`<a href="${hero.screenshotUrl}"><img src="${hero.screenshotUrl}" width="${HERO_WIDTH}" alt="${hero.testName}"></a>`,
|
|
6337
|
-
""
|
|
6338
|
-
);
|
|
6339
|
-
}
|
|
6340
|
-
lines.push(`[View project dashboard on muggle-ai.com](${dashboard})`);
|
|
6341
|
-
return lines.join("\n");
|
|
6342
|
-
}
|
|
6343
|
-
function renderRow(test) {
|
|
6344
|
-
const link = `[${test.name}](${test.viewUrl})`;
|
|
6345
|
-
if (test.status === "passed") {
|
|
6346
|
-
const lastStep = test.steps[test.steps.length - 1];
|
|
6347
|
-
const thumb2 = lastStep ? thumbnail(lastStep.screenshotUrl, ROW_THUMB_WIDTH) : "\u2014";
|
|
6348
|
-
return `| ${link} | \u2705 PASSED | ${thumb2} |`;
|
|
6349
|
-
}
|
|
6350
|
-
const failStep = test.steps.find((s) => s.stepIndex === test.failureStepIndex);
|
|
6351
|
-
const thumb = failStep ? thumbnail(failStep.screenshotUrl, ROW_THUMB_WIDTH) : "\u2014";
|
|
6352
|
-
return `| ${link} | \u274C FAILED \u2014 ${test.error} | ${thumb} |`;
|
|
6353
|
-
}
|
|
6354
|
-
function renderFailureDetails(test) {
|
|
6355
|
-
const stepCount = test.steps.length;
|
|
6356
|
-
const header2 = `<details>
|
|
6357
|
-
<summary>\u{1F4F8} <strong>${test.name}</strong> \u2014 ${stepCount} steps (failed at step ${test.failureStepIndex})</summary>
|
|
6358
|
-
|
|
6359
|
-
| # | Action | Screenshot |
|
|
6360
|
-
|---|--------|------------|`;
|
|
6361
|
-
const rows = test.steps.map((step) => renderFailureStepRow(step, test)).join("\n");
|
|
6362
|
-
return `${header2}
|
|
6363
|
-
${rows}
|
|
6364
|
-
|
|
6365
|
-
</details>`;
|
|
6366
|
-
}
|
|
6367
|
-
function renderFailureStepRow(step, test) {
|
|
6368
|
-
const isFailure = step.stepIndex === test.failureStepIndex;
|
|
6369
|
-
const marker = isFailure ? `${step.stepIndex} \u26A0\uFE0F` : String(step.stepIndex);
|
|
6370
|
-
const action = isFailure ? `${step.action} \u2014 **${test.error}**` : step.action;
|
|
6371
|
-
return `| ${marker} | ${action} | ${thumbnail(step.screenshotUrl, DETAIL_THUMB_WIDTH)} |`;
|
|
6372
|
-
}
|
|
6373
|
-
function renderRowsTable(report) {
|
|
6374
|
-
if (report.tests.length === 0) {
|
|
6375
|
-
return "_No tests were executed._";
|
|
6376
|
-
}
|
|
6377
|
-
const header2 = "| Test Case | Status | Evidence |\n|-----------|--------|----------|";
|
|
6378
|
-
const rows = report.tests.map(renderRow).join("\n");
|
|
6379
|
-
return `${header2}
|
|
6380
|
-
${rows}`;
|
|
6381
|
-
}
|
|
6382
|
-
function renderBody(report, opts) {
|
|
6383
|
-
const sections = [
|
|
6384
|
-
"## E2E Acceptance Results",
|
|
6385
|
-
"",
|
|
6386
|
-
renderSummary(report),
|
|
6387
|
-
"",
|
|
6388
|
-
renderRowsTable(report)
|
|
6389
|
-
];
|
|
6390
|
-
const failures = report.tests.filter((t) => t.status === "failed");
|
|
6391
|
-
if (failures.length > 0) {
|
|
6392
|
-
if (opts.inlineFailureDetails) {
|
|
6393
|
-
sections.push("", ...failures.map(renderFailureDetails));
|
|
6394
|
-
} else {
|
|
6395
|
-
sections.push(
|
|
6396
|
-
"",
|
|
6397
|
-
"_Full step-by-step evidence in the comment below \u2014 the PR description was too large to inline it._"
|
|
6398
|
-
);
|
|
6399
|
-
}
|
|
6400
|
-
}
|
|
6401
|
-
return sections.join("\n");
|
|
6402
|
-
}
|
|
6403
|
-
function renderComment(report) {
|
|
6404
|
-
const failures = report.tests.filter((t) => t.status === "failed");
|
|
6405
|
-
if (failures.length === 0) {
|
|
6406
|
-
return "";
|
|
6407
|
-
}
|
|
6408
|
-
const sections = [
|
|
6409
|
-
"## E2E acceptance evidence (overflow)",
|
|
6410
|
-
"",
|
|
6411
|
-
"_This comment was posted because the full step-by-step evidence did not fit in the PR description._",
|
|
6412
|
-
"",
|
|
6413
|
-
...failures.map(renderFailureDetails)
|
|
6414
|
-
];
|
|
6415
|
-
return sections.join("\n");
|
|
6416
|
-
}
|
|
6417
|
-
|
|
6418
|
-
// src/cli/pr-section/overflow.ts
|
|
6419
|
-
function splitWithOverflow(report, opts) {
|
|
6420
|
-
const inlineBody = renderBody(report, { inlineFailureDetails: true });
|
|
6421
|
-
const inlineBytes = Buffer.byteLength(inlineBody, "utf-8");
|
|
6422
|
-
if (inlineBytes <= opts.maxBodyBytes) {
|
|
6423
|
-
return { body: inlineBody, comment: null };
|
|
6424
|
-
}
|
|
6425
|
-
const spilledBody = renderBody(report, { inlineFailureDetails: false });
|
|
6426
|
-
const comment = renderComment(report);
|
|
6427
|
-
return {
|
|
6428
|
-
body: spilledBody,
|
|
6429
|
-
comment: comment.length > 0 ? comment : null
|
|
6430
|
-
};
|
|
6431
|
-
}
|
|
6432
|
-
var StepSchema = z.object({
|
|
6433
|
-
stepIndex: z.number().int().nonnegative(),
|
|
6434
|
-
action: z.string().min(1),
|
|
6435
|
-
screenshotUrl: z.string().url()
|
|
6436
|
-
});
|
|
6437
|
-
var PassedTestSchema = z.object({
|
|
6438
|
-
name: z.string().min(1),
|
|
6439
|
-
testCaseId: z.string().min(1),
|
|
6440
|
-
testScriptId: z.string().min(1).optional(),
|
|
6441
|
-
runId: z.string().min(1),
|
|
6442
|
-
viewUrl: z.string().url(),
|
|
6443
|
-
status: z.literal("passed"),
|
|
6444
|
-
steps: z.array(StepSchema)
|
|
6445
|
-
});
|
|
6446
|
-
var FailedTestSchema = z.object({
|
|
6447
|
-
name: z.string().min(1),
|
|
6448
|
-
testCaseId: z.string().min(1),
|
|
6449
|
-
testScriptId: z.string().min(1).optional(),
|
|
6450
|
-
runId: z.string().min(1),
|
|
6451
|
-
viewUrl: z.string().url(),
|
|
6452
|
-
status: z.literal("failed"),
|
|
6453
|
-
steps: z.array(StepSchema),
|
|
6454
|
-
failureStepIndex: z.number().int().nonnegative(),
|
|
6455
|
-
error: z.string().min(1),
|
|
6456
|
-
artifactsDir: z.string().min(1).optional()
|
|
6457
|
-
});
|
|
6458
|
-
var TestResultSchema = z.discriminatedUnion("status", [
|
|
6459
|
-
PassedTestSchema,
|
|
6460
|
-
FailedTestSchema
|
|
6461
|
-
]);
|
|
6462
|
-
var E2eReportSchema = z.object({
|
|
6463
|
-
projectId: z.string().min(1),
|
|
6464
|
-
tests: z.array(TestResultSchema)
|
|
6465
|
-
});
|
|
6466
|
-
|
|
6467
|
-
// src/cli/pr-section/index.ts
|
|
6468
|
-
function buildPrSection(report, opts) {
|
|
6469
|
-
return splitWithOverflow(report, opts);
|
|
6470
|
-
}
|
|
6471
|
-
|
|
6472
|
-
// src/cli/build-pr-section.ts
|
|
6473
|
-
var DEFAULT_MAX_BODY_BYTES = 6e4;
|
|
6474
|
-
async function readAll(stream) {
|
|
6475
|
-
const chunks = [];
|
|
6476
|
-
for await (const chunk of stream) {
|
|
6477
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
6478
|
-
}
|
|
6479
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
6480
|
-
}
|
|
6481
|
-
function errMsg(e) {
|
|
6482
|
-
return e instanceof Error ? e.message : String(e);
|
|
6483
|
-
}
|
|
6484
|
-
async function runBuildPrSection(opts) {
|
|
6485
|
-
let raw;
|
|
6486
|
-
try {
|
|
6487
|
-
raw = await readAll(opts.stdin);
|
|
6488
|
-
} catch (err) {
|
|
6489
|
-
opts.stderrWrite(`build-pr-section: failed to read stdin: ${errMsg(err)}
|
|
6490
|
-
`);
|
|
6491
|
-
return 1;
|
|
6492
|
-
}
|
|
6493
|
-
let json;
|
|
6494
|
-
try {
|
|
6495
|
-
json = JSON.parse(raw);
|
|
6496
|
-
} catch (err) {
|
|
6497
|
-
opts.stderrWrite(`build-pr-section: failed to parse stdin as JSON: ${errMsg(err)}
|
|
6498
|
-
`);
|
|
6499
|
-
return 1;
|
|
6500
|
-
}
|
|
6501
|
-
let report;
|
|
6502
|
-
try {
|
|
6503
|
-
report = E2eReportSchema.parse(json);
|
|
6504
|
-
} catch (err) {
|
|
6505
|
-
if (err instanceof ZodError) {
|
|
6506
|
-
opts.stderrWrite(
|
|
6507
|
-
`build-pr-section: report validation failed:
|
|
6508
|
-
${err.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}
|
|
6509
|
-
`
|
|
6510
|
-
);
|
|
6511
|
-
} else {
|
|
6512
|
-
opts.stderrWrite(`build-pr-section: report validation failed: ${errMsg(err)}
|
|
6513
|
-
`);
|
|
6514
|
-
}
|
|
6515
|
-
return 1;
|
|
6516
|
-
}
|
|
6517
|
-
const result = buildPrSection(report, { maxBodyBytes: opts.maxBodyBytes });
|
|
6518
|
-
opts.stdoutWrite(JSON.stringify({ body: result.body, comment: result.comment }));
|
|
6519
|
-
return 0;
|
|
6520
|
-
}
|
|
6521
|
-
async function buildPrSectionCommand(options) {
|
|
6522
|
-
const maxBodyBytes = options.maxBodyBytes ? Number(options.maxBodyBytes) : DEFAULT_MAX_BODY_BYTES;
|
|
6523
|
-
if (!Number.isFinite(maxBodyBytes) || maxBodyBytes <= 0) {
|
|
6524
|
-
process.stderr.write(`build-pr-section: --max-body-bytes must be a positive number
|
|
6525
|
-
`);
|
|
6526
|
-
process.exitCode = 1;
|
|
6527
|
-
return;
|
|
6528
|
-
}
|
|
6529
|
-
const code = await runBuildPrSection({
|
|
6530
|
-
stdin: process.stdin,
|
|
6531
|
-
stdoutWrite: (s) => process.stdout.write(s),
|
|
6532
|
-
stderrWrite: (s) => process.stderr.write(s),
|
|
6533
|
-
maxBodyBytes
|
|
6534
|
-
});
|
|
6535
|
-
if (code !== 0) {
|
|
6536
|
-
process.exitCode = code;
|
|
6537
|
-
}
|
|
6538
|
-
}
|
|
6539
|
-
var logger7 = getLogger();
|
|
6540
|
-
var ELECTRON_APP_DIR2 = "electron-app";
|
|
6541
|
-
var CURSOR_SKILLS_DIR = ".cursor";
|
|
6542
|
-
var CURSOR_SKILLS_SUBDIR = "skills";
|
|
6543
|
-
var MUGGLE_SKILL_PREFIX = "muggle";
|
|
6544
|
-
var INSTALL_MANIFEST_FILE = "install-manifest.json";
|
|
6545
|
-
function getElectronAppBaseDir() {
|
|
6546
|
-
return path2.join(getDataDir2(), ELECTRON_APP_DIR2);
|
|
6547
|
-
}
|
|
6548
|
-
function getCursorSkillsDir() {
|
|
6549
|
-
return path2.join(homedir(), CURSOR_SKILLS_DIR, CURSOR_SKILLS_SUBDIR);
|
|
6550
|
-
}
|
|
6551
|
-
function getInstallManifestPath() {
|
|
6552
|
-
return path2.join(getDataDir2(), INSTALL_MANIFEST_FILE);
|
|
6553
|
-
}
|
|
6554
|
-
function readInstallManifest() {
|
|
6555
|
-
const manifestPath = getInstallManifestPath();
|
|
6556
|
-
if (!existsSync(manifestPath)) {
|
|
6557
|
-
return null;
|
|
6558
|
-
}
|
|
6559
|
-
try {
|
|
6560
|
-
const content = readFileSync(manifestPath, "utf-8");
|
|
6561
|
-
const manifest = JSON.parse(content);
|
|
6562
|
-
if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) {
|
|
6563
|
-
return null;
|
|
6564
|
-
}
|
|
6565
|
-
return manifest;
|
|
6566
|
-
} catch {
|
|
6567
|
-
return null;
|
|
6568
|
-
}
|
|
6569
|
-
}
|
|
6570
|
-
function listObsoleteSkills() {
|
|
6571
|
-
const skillsDir = getCursorSkillsDir();
|
|
6572
|
-
const manifest = readInstallManifest();
|
|
6573
|
-
const obsoleteSkills = [];
|
|
6574
|
-
if (!existsSync(skillsDir)) {
|
|
6575
|
-
return obsoleteSkills;
|
|
6576
|
-
}
|
|
6577
|
-
const manifestSkills = new Set(manifest?.skills ?? []);
|
|
6578
|
-
try {
|
|
6579
|
-
const entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
6580
|
-
for (const entry of entries) {
|
|
6581
|
-
if (!entry.isDirectory()) {
|
|
6582
|
-
continue;
|
|
6583
|
-
}
|
|
6584
|
-
if (!entry.name.startsWith(MUGGLE_SKILL_PREFIX)) {
|
|
6585
|
-
continue;
|
|
6586
|
-
}
|
|
6587
|
-
if (manifestSkills.has(entry.name)) {
|
|
6588
|
-
continue;
|
|
6589
|
-
}
|
|
6590
|
-
const skillPath = path2.join(skillsDir, entry.name);
|
|
6591
|
-
const sizeBytes = getDirectorySize(skillPath);
|
|
6592
|
-
obsoleteSkills.push({
|
|
6593
|
-
name: entry.name,
|
|
6594
|
-
path: skillPath,
|
|
6595
|
-
sizeBytes
|
|
6596
|
-
});
|
|
6597
|
-
}
|
|
6598
|
-
} catch (error) {
|
|
6599
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6600
|
-
logger7.warn("Failed to list obsolete skills", { error: errorMessage });
|
|
6601
|
-
}
|
|
6602
|
-
return obsoleteSkills;
|
|
6603
|
-
}
|
|
6604
|
-
function cleanupObsoleteSkills(options = {}) {
|
|
6605
|
-
const { dryRun = false } = options;
|
|
6606
|
-
const obsoleteSkills = listObsoleteSkills();
|
|
6607
|
-
const removed = [];
|
|
6608
|
-
let freedBytes = 0;
|
|
6609
|
-
for (const skill of obsoleteSkills) {
|
|
6610
|
-
if (!dryRun) {
|
|
6611
|
-
try {
|
|
6612
|
-
rmSync(skill.path, { recursive: true, force: true });
|
|
6613
|
-
logger7.info("Removed obsolete skill", {
|
|
6614
|
-
skill: skill.name,
|
|
6615
|
-
freedBytes: skill.sizeBytes
|
|
6616
|
-
});
|
|
6617
|
-
} catch (error) {
|
|
6618
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6619
|
-
logger7.error("Failed to remove skill", {
|
|
6620
|
-
skill: skill.name,
|
|
6621
|
-
error: errorMessage
|
|
6622
|
-
});
|
|
6623
|
-
continue;
|
|
6624
|
-
}
|
|
6625
|
-
}
|
|
6626
|
-
removed.push(skill);
|
|
6627
|
-
freedBytes += skill.sizeBytes;
|
|
6628
|
-
}
|
|
6629
|
-
return { removed, freedBytes };
|
|
6630
|
-
}
|
|
6631
|
-
function getDirectorySize(dirPath) {
|
|
6632
|
-
let totalSize = 0;
|
|
6633
|
-
try {
|
|
6634
|
-
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
6635
|
-
for (const entry of entries) {
|
|
6636
|
-
const fullPath = path2.join(dirPath, entry.name);
|
|
6637
|
-
if (entry.isDirectory()) {
|
|
6638
|
-
totalSize += getDirectorySize(fullPath);
|
|
6639
|
-
} else if (entry.isFile()) {
|
|
6640
|
-
try {
|
|
6641
|
-
const stats = statSync(fullPath);
|
|
6642
|
-
totalSize += stats.size;
|
|
6643
|
-
} catch {
|
|
6644
|
-
}
|
|
6645
|
-
}
|
|
6646
|
-
}
|
|
6647
|
-
} catch {
|
|
6648
|
-
}
|
|
6649
|
-
return totalSize;
|
|
6650
|
-
}
|
|
6651
|
-
function formatBytes(bytes) {
|
|
6652
|
-
if (bytes === 0) {
|
|
6653
|
-
return "0 B";
|
|
6654
|
-
}
|
|
6655
|
-
const units = ["B", "KB", "MB", "GB"];
|
|
6656
|
-
const k = 1024;
|
|
6657
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
6658
|
-
const size = bytes / Math.pow(k, i);
|
|
6659
|
-
return `${size.toFixed(1)} ${units[i]}`;
|
|
6660
|
-
}
|
|
6661
|
-
function compareVersions(a, b) {
|
|
6662
|
-
const partsA = a.split(".").map(Number);
|
|
6663
|
-
const partsB = b.split(".").map(Number);
|
|
6664
|
-
for (let i = 0; i < 3; i++) {
|
|
6665
|
-
const partA = partsA[i] || 0;
|
|
6666
|
-
const partB = partsB[i] || 0;
|
|
6667
|
-
if (partA !== partB) {
|
|
6668
|
-
return partA - partB;
|
|
6669
|
-
}
|
|
6670
|
-
}
|
|
6671
|
-
return 0;
|
|
6672
|
-
}
|
|
6673
|
-
function listInstalledVersions() {
|
|
6674
|
-
const baseDir = getElectronAppBaseDir();
|
|
6675
|
-
const currentVersion = getElectronAppVersion();
|
|
6676
|
-
const versions = [];
|
|
6677
|
-
if (!existsSync(baseDir)) {
|
|
6678
|
-
return versions;
|
|
6679
|
-
}
|
|
6680
|
-
try {
|
|
6681
|
-
const entries = readdirSync(baseDir, { withFileTypes: true });
|
|
6682
|
-
for (const entry of entries) {
|
|
6683
|
-
if (!entry.isDirectory()) {
|
|
6684
|
-
continue;
|
|
6685
|
-
}
|
|
6686
|
-
if (!/^\d+\.\d+\.\d+$/.test(entry.name)) {
|
|
6687
|
-
continue;
|
|
6688
|
-
}
|
|
6689
|
-
const versionPath = path2.join(baseDir, entry.name);
|
|
6690
|
-
const sizeBytes = getDirectorySize(versionPath);
|
|
6691
|
-
versions.push({
|
|
6692
|
-
version: entry.name,
|
|
6693
|
-
path: versionPath,
|
|
6694
|
-
sizeBytes,
|
|
6695
|
-
isCurrent: entry.name === currentVersion
|
|
6696
|
-
});
|
|
6697
|
-
}
|
|
6698
|
-
} catch (error) {
|
|
6699
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6700
|
-
logger7.warn("Failed to list installed versions", { error: errorMessage });
|
|
6701
|
-
}
|
|
6702
|
-
versions.sort((a, b) => compareVersions(b.version, a.version));
|
|
6703
|
-
return versions;
|
|
6704
|
-
}
|
|
6705
|
-
function cleanupOldVersions(options = {}) {
|
|
6706
|
-
const { all = false, dryRun = false } = options;
|
|
6707
|
-
const versions = listInstalledVersions();
|
|
6708
|
-
const removed = [];
|
|
6709
|
-
let freedBytes = 0;
|
|
6710
|
-
const versionsToKeep = all ? 1 : 2;
|
|
6711
|
-
let keptCount = 0;
|
|
6712
|
-
for (const version of versions) {
|
|
6713
|
-
if (version.isCurrent) {
|
|
6714
|
-
keptCount++;
|
|
6715
|
-
continue;
|
|
6716
|
-
}
|
|
6717
|
-
if (keptCount < versionsToKeep) {
|
|
6718
|
-
keptCount++;
|
|
6719
|
-
continue;
|
|
6720
|
-
}
|
|
6721
|
-
if (!dryRun) {
|
|
6722
|
-
try {
|
|
6723
|
-
rmSync(version.path, { recursive: true, force: true });
|
|
6724
|
-
logger7.info("Removed old version", {
|
|
6725
|
-
version: version.version,
|
|
6726
|
-
freedBytes: version.sizeBytes
|
|
6727
|
-
});
|
|
6728
|
-
} catch (error) {
|
|
6729
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6730
|
-
logger7.error("Failed to remove version", {
|
|
6731
|
-
version: version.version,
|
|
6732
|
-
error: errorMessage
|
|
6733
|
-
});
|
|
6734
|
-
continue;
|
|
6735
|
-
}
|
|
6736
|
-
}
|
|
6737
|
-
removed.push(version);
|
|
6738
|
-
freedBytes += version.sizeBytes;
|
|
6739
|
-
}
|
|
6740
|
-
return { removed, freedBytes };
|
|
6741
|
-
}
|
|
6742
|
-
async function versionsCommand() {
|
|
6743
|
-
console.log("\nInstalled Electron App Versions");
|
|
6744
|
-
console.log("================================\n");
|
|
6745
|
-
const versions = listInstalledVersions();
|
|
6746
|
-
if (versions.length === 0) {
|
|
6747
|
-
console.log("No versions installed.");
|
|
6748
|
-
console.log("Run 'muggle setup' to download the Electron app.\n");
|
|
6749
|
-
return;
|
|
6750
|
-
}
|
|
6751
|
-
let totalSize = 0;
|
|
6752
|
-
for (const version of versions) {
|
|
6753
|
-
const marker = version.isCurrent ? " (current)" : "";
|
|
6754
|
-
const size = formatBytes(version.sizeBytes);
|
|
6755
|
-
console.log(` v${version.version}${marker} - ${size}`);
|
|
6756
|
-
totalSize += version.sizeBytes;
|
|
6757
|
-
}
|
|
6758
|
-
console.log("");
|
|
6759
|
-
console.log(`Total: ${versions.length} version(s), ${formatBytes(totalSize)}`);
|
|
6760
|
-
console.log("");
|
|
6761
|
-
}
|
|
6762
|
-
async function cleanupCommand(options) {
|
|
6763
|
-
let totalFreedBytes = 0;
|
|
6764
|
-
let totalRemovedCount = 0;
|
|
6765
|
-
console.log("\nElectron App Cleanup");
|
|
6766
|
-
console.log("====================\n");
|
|
6767
|
-
const versions = listInstalledVersions();
|
|
6768
|
-
if (versions.length === 0) {
|
|
6769
|
-
console.log("No versions installed. Nothing to clean up.\n");
|
|
6770
|
-
} else if (versions.length === 1) {
|
|
6771
|
-
console.log("Only the current version is installed. Nothing to clean up.\n");
|
|
6772
|
-
} else {
|
|
6773
|
-
const currentVersion = versions.find((v) => v.isCurrent);
|
|
6774
|
-
const oldVersions = versions.filter((v) => !v.isCurrent);
|
|
6775
|
-
console.log(`Current version: v${currentVersion?.version ?? "unknown"}`);
|
|
6776
|
-
console.log(`Old versions: ${oldVersions.length}`);
|
|
6777
|
-
console.log("");
|
|
6778
|
-
if (options.dryRun) {
|
|
6779
|
-
console.log("Dry run - showing what would be deleted:\n");
|
|
6780
|
-
}
|
|
6781
|
-
const result = cleanupOldVersions(options);
|
|
6782
|
-
if (result.removed.length === 0) {
|
|
6783
|
-
if (options.all) {
|
|
6784
|
-
console.log("No old versions to remove.\n");
|
|
6785
|
-
} else {
|
|
6786
|
-
console.log("Keeping one previous version for rollback.");
|
|
6787
|
-
console.log("Use --all to remove all old versions.\n");
|
|
6788
|
-
}
|
|
6789
|
-
} else {
|
|
6790
|
-
console.log(options.dryRun ? "Would remove:" : "Removed:");
|
|
6791
|
-
for (const version of result.removed) {
|
|
6792
|
-
console.log(` v${version.version} (${formatBytes(version.sizeBytes)})`);
|
|
6793
|
-
}
|
|
6794
|
-
totalFreedBytes += result.freedBytes;
|
|
6795
|
-
totalRemovedCount += result.removed.length;
|
|
6796
|
-
console.log("");
|
|
6797
|
-
}
|
|
6798
|
-
}
|
|
6799
|
-
if (options.skills) {
|
|
6800
|
-
console.log("Skills Cleanup");
|
|
6801
|
-
console.log("==============\n");
|
|
6802
|
-
const obsoleteSkills = listObsoleteSkills();
|
|
6803
|
-
if (obsoleteSkills.length === 0) {
|
|
6804
|
-
console.log("No obsolete skills found. Nothing to clean up.\n");
|
|
6805
|
-
} else {
|
|
6806
|
-
console.log(`Found ${obsoleteSkills.length} obsolete skill(s):
|
|
6807
|
-
`);
|
|
6808
|
-
if (options.dryRun) {
|
|
6809
|
-
console.log("Dry run - showing what would be deleted:\n");
|
|
6810
|
-
}
|
|
6811
|
-
const skillResult = cleanupObsoleteSkills({ dryRun: options.dryRun });
|
|
6812
|
-
console.log(options.dryRun ? "Would remove:" : "Removed:");
|
|
6813
|
-
for (const skill of skillResult.removed) {
|
|
6814
|
-
console.log(` ${skill.name} (${formatBytes(skill.sizeBytes)})`);
|
|
6815
|
-
}
|
|
6816
|
-
totalFreedBytes += skillResult.freedBytes;
|
|
6817
|
-
totalRemovedCount += skillResult.removed.length;
|
|
6818
|
-
console.log("");
|
|
6819
|
-
}
|
|
6820
|
-
}
|
|
6821
|
-
if (totalRemovedCount > 0) {
|
|
6822
|
-
console.log(
|
|
6823
|
-
`${options.dryRun ? "Would free" : "Freed"}: ${formatBytes(totalFreedBytes)} total`
|
|
6824
|
-
);
|
|
6825
|
-
console.log("");
|
|
6826
|
-
if (options.dryRun) {
|
|
6827
|
-
console.log("Run without --dry-run to actually delete.\n");
|
|
6828
|
-
}
|
|
6829
|
-
}
|
|
6830
|
-
logger7.info("Cleanup completed", {
|
|
6831
|
-
removed: totalRemovedCount,
|
|
6832
|
-
freedBytes: totalFreedBytes,
|
|
6833
|
-
dryRun: options.dryRun,
|
|
6834
|
-
includeSkills: options.skills
|
|
6835
|
-
});
|
|
6836
|
-
}
|
|
6837
|
-
var logger8 = getLogger();
|
|
6838
|
-
function getCursorMcpConfigPath() {
|
|
6839
|
-
return join(homedir(), ".cursor", "mcp.json");
|
|
6840
|
-
}
|
|
6841
|
-
function getExpectedExecutablePath(versionDir) {
|
|
6842
|
-
const os4 = platform();
|
|
6843
|
-
switch (os4) {
|
|
6844
|
-
case "darwin":
|
|
6845
|
-
return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
6846
|
-
case "win32":
|
|
6847
|
-
return path2.join(versionDir, "MuggleAI.exe");
|
|
6848
|
-
case "linux":
|
|
6849
|
-
return path2.join(versionDir, "MuggleAI");
|
|
6850
|
-
default:
|
|
6851
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
6852
|
-
}
|
|
6853
|
-
}
|
|
6854
|
-
function verifyElectronAppInstallation() {
|
|
6855
|
-
const version = getElectronAppVersion();
|
|
6856
|
-
const versionDir = getElectronAppDir(version);
|
|
6857
|
-
const executablePath = getExpectedExecutablePath(versionDir);
|
|
6858
|
-
const metadataPath = path2.join(versionDir, ".install-metadata.json");
|
|
6859
|
-
const result = {
|
|
6860
|
-
valid: false,
|
|
6861
|
-
versionDir,
|
|
6862
|
-
executablePath,
|
|
6863
|
-
executableExists: false,
|
|
6864
|
-
executableIsFile: false,
|
|
6865
|
-
metadataExists: false,
|
|
6866
|
-
hasPartialArchive: false
|
|
6867
|
-
};
|
|
6868
|
-
if (!fs3.existsSync(versionDir)) {
|
|
6869
|
-
result.errorDetail = "Version directory does not exist";
|
|
6870
|
-
return result;
|
|
6871
|
-
}
|
|
6872
|
-
const archivePatterns = ["MuggleAI-darwin", "MuggleAI-win32", "MuggleAI-linux"];
|
|
6873
|
-
try {
|
|
6874
|
-
const files = fs3.readdirSync(versionDir);
|
|
6875
|
-
for (const file of files) {
|
|
6876
|
-
if (archivePatterns.some((pattern) => file.startsWith(pattern)) && (file.endsWith(".zip") || file.endsWith(".tar.gz"))) {
|
|
6877
|
-
result.hasPartialArchive = true;
|
|
6878
|
-
break;
|
|
6879
|
-
}
|
|
6880
|
-
}
|
|
6881
|
-
} catch {
|
|
6882
|
-
}
|
|
6883
|
-
result.executableExists = fs3.existsSync(executablePath);
|
|
6884
|
-
if (!result.executableExists) {
|
|
6885
|
-
if (result.hasPartialArchive) {
|
|
6886
|
-
result.errorDetail = "Download incomplete: archive found but not extracted";
|
|
6887
|
-
} else {
|
|
6888
|
-
result.errorDetail = "Executable not found at expected path";
|
|
6889
|
-
}
|
|
6890
|
-
return result;
|
|
6891
|
-
}
|
|
6892
|
-
try {
|
|
6893
|
-
const stats = fs3.statSync(executablePath);
|
|
6894
|
-
result.executableIsFile = stats.isFile();
|
|
6895
|
-
if (!result.executableIsFile) {
|
|
6896
|
-
result.errorDetail = "Executable path exists but is not a file";
|
|
6897
|
-
return result;
|
|
6898
|
-
}
|
|
6899
|
-
} catch {
|
|
6900
|
-
result.errorDetail = "Cannot stat executable (broken symlink?)";
|
|
6901
|
-
return result;
|
|
6902
|
-
}
|
|
6903
|
-
result.metadataExists = fs3.existsSync(metadataPath);
|
|
6904
|
-
result.valid = true;
|
|
6905
|
-
return result;
|
|
6906
|
-
}
|
|
6907
|
-
function validateCursorMcpConfig() {
|
|
6908
|
-
const cursorMcpConfigPath = getCursorMcpConfigPath();
|
|
6909
|
-
if (!existsSync(cursorMcpConfigPath)) {
|
|
6910
|
-
return {
|
|
6911
|
-
passed: false,
|
|
6912
|
-
description: `Missing at ${cursorMcpConfigPath}`
|
|
6913
|
-
};
|
|
6914
|
-
}
|
|
6915
|
-
try {
|
|
6916
|
-
const rawCursorConfig = JSON.parse(
|
|
6917
|
-
readFileSync(cursorMcpConfigPath, "utf-8")
|
|
6918
|
-
);
|
|
6919
|
-
if (!rawCursorConfig.mcpServers) {
|
|
6920
|
-
return {
|
|
6921
|
-
passed: false,
|
|
6922
|
-
description: "Missing mcpServers key"
|
|
6923
|
-
};
|
|
6924
|
-
}
|
|
6925
|
-
const muggleServerConfig = rawCursorConfig.mcpServers.muggle;
|
|
6926
|
-
if (!muggleServerConfig) {
|
|
6927
|
-
return {
|
|
6928
|
-
passed: false,
|
|
6929
|
-
description: "Missing mcpServers.muggle entry"
|
|
6930
|
-
};
|
|
6931
|
-
}
|
|
6932
|
-
if (!Array.isArray(muggleServerConfig.args)) {
|
|
6933
|
-
return {
|
|
6934
|
-
passed: false,
|
|
6935
|
-
description: "mcpServers.muggle.args is not an array"
|
|
6936
|
-
};
|
|
6937
|
-
}
|
|
6938
|
-
const hasServeArgument = muggleServerConfig.args.includes("serve");
|
|
6939
|
-
if (!hasServeArgument) {
|
|
6940
|
-
return {
|
|
6941
|
-
passed: false,
|
|
6942
|
-
description: "mcpServers.muggle args does not include 'serve'"
|
|
6943
|
-
};
|
|
6944
|
-
}
|
|
6945
|
-
if (muggleServerConfig.command === "node") {
|
|
6946
|
-
const firstArgument = muggleServerConfig.args.at(0);
|
|
6947
|
-
if (!firstArgument) {
|
|
6948
|
-
return {
|
|
6949
|
-
passed: false,
|
|
6950
|
-
description: "mcpServers.muggle command is node but args[0] is missing"
|
|
6951
|
-
};
|
|
6952
|
-
}
|
|
6953
|
-
if (!existsSync(firstArgument)) {
|
|
6954
|
-
return {
|
|
6955
|
-
passed: false,
|
|
6956
|
-
description: `mcpServers.muggle args[0] does not exist: ${firstArgument}`
|
|
6957
|
-
};
|
|
6958
|
-
}
|
|
6959
|
-
}
|
|
6960
|
-
return {
|
|
6961
|
-
passed: true,
|
|
6962
|
-
description: `Configured at ${cursorMcpConfigPath}`
|
|
6963
|
-
};
|
|
6964
|
-
} catch (error) {
|
|
6965
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6966
|
-
return {
|
|
6967
|
-
passed: false,
|
|
6968
|
-
description: `Invalid JSON or schema: ${errorMessage}`
|
|
6969
|
-
};
|
|
6970
|
-
}
|
|
6971
|
-
}
|
|
6972
|
-
function runDiagnostics() {
|
|
6973
|
-
const results = [];
|
|
6974
|
-
const config = getConfig();
|
|
6975
|
-
const dataDir = getDataDir2();
|
|
6976
|
-
results.push({
|
|
6977
|
-
name: "Data Directory",
|
|
6978
|
-
passed: existsSync(dataDir),
|
|
6979
|
-
description: existsSync(dataDir) ? `Found at ${dataDir}` : `Not found at ${dataDir}`,
|
|
6980
|
-
suggestion: "Run 'muggle login' to create the data directory"
|
|
6981
|
-
});
|
|
6982
|
-
const electronVersion = getElectronAppVersion();
|
|
6983
|
-
const bundledVersion = getBundledElectronAppVersion();
|
|
6984
|
-
const versionSource = getElectronAppVersionSource();
|
|
6985
|
-
const installVerification = verifyElectronAppInstallation();
|
|
6986
|
-
let electronDescription;
|
|
6987
|
-
let electronSuggestion;
|
|
6988
|
-
if (installVerification.valid) {
|
|
6989
|
-
electronDescription = `Installed (v${electronVersion})`;
|
|
6990
|
-
switch (versionSource) {
|
|
6991
|
-
case "env":
|
|
6992
|
-
electronDescription += ` [from ELECTRON_APP_VERSION env]`;
|
|
6993
|
-
break;
|
|
6994
|
-
case "override":
|
|
6995
|
-
electronDescription += ` [overridden from bundled v${bundledVersion}]`;
|
|
6996
|
-
break;
|
|
6997
|
-
}
|
|
6998
|
-
if (!installVerification.metadataExists) {
|
|
6999
|
-
electronDescription += " [missing metadata]";
|
|
7000
|
-
}
|
|
7001
|
-
} else {
|
|
7002
|
-
electronDescription = `Not installed (expected v${electronVersion})`;
|
|
7003
|
-
if (installVerification.errorDetail) {
|
|
7004
|
-
electronDescription += `
|
|
7005
|
-
\u2514\u2500 ${installVerification.errorDetail}`;
|
|
7006
|
-
electronDescription += `
|
|
7007
|
-
\u2514\u2500 Checked: ${installVerification.versionDir}`;
|
|
7008
|
-
}
|
|
7009
|
-
if (installVerification.hasPartialArchive) {
|
|
7010
|
-
electronSuggestion = "Run 'muggle setup --force' to re-download and extract";
|
|
7011
|
-
} else {
|
|
7012
|
-
electronSuggestion = "Run 'muggle setup' to download the Electron app";
|
|
7013
|
-
}
|
|
7014
|
-
}
|
|
7015
|
-
results.push({
|
|
7016
|
-
name: "Electron App",
|
|
7017
|
-
passed: installVerification.valid,
|
|
7018
|
-
description: electronDescription,
|
|
7019
|
-
suggestion: electronSuggestion
|
|
7020
|
-
});
|
|
7021
|
-
if (installVerification.valid) {
|
|
7022
|
-
results.push({
|
|
7023
|
-
name: "Electron App Updates",
|
|
7024
|
-
passed: true,
|
|
7025
|
-
description: "Run 'muggle upgrade --check' to check for updates"
|
|
7026
|
-
});
|
|
7027
|
-
}
|
|
7028
|
-
const authService = getAuthService();
|
|
7029
|
-
const authStatus = authService.getAuthStatus();
|
|
7030
|
-
results.push({
|
|
7031
|
-
name: "Authentication",
|
|
7032
|
-
passed: authStatus.authenticated,
|
|
7033
|
-
description: authStatus.authenticated ? `Authenticated as ${authStatus.email ?? "unknown"}` : "Not authenticated",
|
|
7034
|
-
suggestion: "Run 'muggle login' to authenticate"
|
|
7035
|
-
});
|
|
7036
|
-
const hasStoredApiKey = hasApiKey();
|
|
7037
|
-
results.push({
|
|
7038
|
-
name: "API Key",
|
|
7039
|
-
passed: hasStoredApiKey,
|
|
7040
|
-
description: hasStoredApiKey ? "API key stored" : "No API key stored (optional)",
|
|
7041
|
-
suggestion: "Run 'muggle login --key-name <name>' to generate an API key"
|
|
7042
|
-
});
|
|
7043
|
-
const credentialsPath = getCredentialsFilePath();
|
|
7044
|
-
results.push({
|
|
7045
|
-
name: "Credentials File",
|
|
7046
|
-
passed: existsSync(credentialsPath),
|
|
7047
|
-
description: existsSync(credentialsPath) ? `Found at ${credentialsPath}` : `Not found at ${credentialsPath}`,
|
|
7048
|
-
suggestion: "Run 'muggle login' to create credentials"
|
|
7049
|
-
});
|
|
7050
|
-
results.push({
|
|
7051
|
-
name: "Prompt Service URL",
|
|
7052
|
-
passed: !!config.e2e.promptServiceBaseUrl,
|
|
7053
|
-
description: config.e2e.promptServiceBaseUrl
|
|
7054
|
-
});
|
|
7055
|
-
results.push({
|
|
7056
|
-
name: "Web Service URL",
|
|
7057
|
-
passed: !!config.localQa.webServiceUrl,
|
|
7058
|
-
description: config.localQa.webServiceUrl
|
|
7059
|
-
});
|
|
7060
|
-
const cursorMcpConfigValidationResult = validateCursorMcpConfig();
|
|
7061
|
-
results.push({
|
|
7062
|
-
name: "Cursor MCP Config",
|
|
7063
|
-
passed: cursorMcpConfigValidationResult.passed,
|
|
7064
|
-
description: cursorMcpConfigValidationResult.description,
|
|
7065
|
-
suggestion: "Re-run npm install -g @muggleai/works to refresh ~/.cursor/mcp.json"
|
|
7066
|
-
});
|
|
7067
|
-
return results;
|
|
7068
|
-
}
|
|
7069
|
-
function formatCheckResult(result) {
|
|
7070
|
-
const icon = result.passed ? "\u2713" : "\u2717";
|
|
7071
|
-
const color = result.passed ? "\x1B[32m" : "\x1B[31m";
|
|
7072
|
-
const reset = "\x1B[0m";
|
|
7073
|
-
let output = `${color}${icon}${reset} ${result.name}: ${result.description}`;
|
|
7074
|
-
if (!result.passed && result.suggestion) {
|
|
7075
|
-
output += `
|
|
7076
|
-
\u2514\u2500 ${result.suggestion}`;
|
|
7077
|
-
}
|
|
7078
|
-
return output;
|
|
7079
|
-
}
|
|
7080
|
-
async function doctorCommand() {
|
|
7081
|
-
console.log("\nMuggle Works Doctor");
|
|
7082
|
-
console.log("=================\n");
|
|
7083
|
-
const results = runDiagnostics();
|
|
7084
|
-
for (const result of results) {
|
|
7085
|
-
console.log(formatCheckResult(result));
|
|
7086
|
-
}
|
|
7087
|
-
console.log("");
|
|
7088
|
-
const failedCount = results.filter((r) => !r.passed).length;
|
|
7089
|
-
if (failedCount === 0) {
|
|
7090
|
-
console.log("All checks passed! Your installation is ready.");
|
|
7091
|
-
} else {
|
|
7092
|
-
console.log(`${failedCount} issue(s) found. See suggestions above.`);
|
|
7093
|
-
}
|
|
7094
|
-
logger8.info("Doctor command completed", {
|
|
7095
|
-
totalChecks: results.length,
|
|
7096
|
-
passed: results.length - failedCount,
|
|
7097
|
-
failed: failedCount
|
|
7098
|
-
});
|
|
7099
|
-
}
|
|
7100
|
-
|
|
7101
|
-
// src/cli/help.ts
|
|
7102
|
-
var COLORS = {
|
|
7103
|
-
reset: "\x1B[0m",
|
|
7104
|
-
bold: "\x1B[1m",
|
|
7105
|
-
dim: "\x1B[2m",
|
|
7106
|
-
cyan: "\x1B[36m",
|
|
7107
|
-
green: "\x1B[32m",
|
|
7108
|
-
yellow: "\x1B[33m",
|
|
7109
|
-
blue: "\x1B[34m"};
|
|
7110
|
-
function colorize(text, color) {
|
|
7111
|
-
if (process.env.NO_COLOR) {
|
|
7112
|
-
return text;
|
|
7113
|
-
}
|
|
7114
|
-
return `${color}${text}${COLORS.reset}`;
|
|
7115
|
-
}
|
|
7116
|
-
function header(title) {
|
|
7117
|
-
return colorize(`
|
|
7118
|
-
${title}`, COLORS.bold + COLORS.cyan);
|
|
7119
|
-
}
|
|
7120
|
-
function cmd(cmd2) {
|
|
7121
|
-
return colorize(cmd2, COLORS.green);
|
|
7122
|
-
}
|
|
7123
|
-
function path12(path15) {
|
|
7124
|
-
return colorize(path15, COLORS.yellow);
|
|
7125
|
-
}
|
|
7126
|
-
function getHelpGuidance() {
|
|
7127
|
-
const lines = [
|
|
7128
|
-
"",
|
|
7129
|
-
colorize("=".repeat(70), COLORS.cyan),
|
|
7130
|
-
colorize(" Muggle AI Works - Comprehensive How-To Guide", COLORS.bold + COLORS.green),
|
|
7131
|
-
colorize("=".repeat(70), COLORS.cyan),
|
|
7132
|
-
"",
|
|
7133
|
-
header("What is Muggle AI Works?"),
|
|
7134
|
-
"",
|
|
7135
|
-
" Muggle AI Works is a Model Context Protocol server that provides AI",
|
|
7136
|
-
" assistants with tools to perform automated end-to-end (E2E) acceptance testing of web applications.",
|
|
7137
|
-
"",
|
|
7138
|
-
" It supports both:",
|
|
7139
|
-
` ${colorize("\u2022", COLORS.green)} Cloud E2E - Test remote production/staging sites with a public URL`,
|
|
7140
|
-
` ${colorize("\u2022", COLORS.green)} Local E2E - Test localhost development servers`,
|
|
7141
|
-
"",
|
|
7142
|
-
header("Setup Instructions"),
|
|
7143
|
-
"",
|
|
7144
|
-
` ${colorize("Step 1:", COLORS.bold)} Configure your MCP client`,
|
|
7145
|
-
"",
|
|
7146
|
-
` For ${colorize("Cursor", COLORS.bold)}, edit ${path12("~/.cursor/mcp.json")}:`,
|
|
7147
|
-
"",
|
|
7148
|
-
` ${colorize("{", COLORS.dim)}`,
|
|
7149
|
-
` ${colorize('"mcpServers"', COLORS.yellow)}: {`,
|
|
7150
|
-
` ${colorize('"muggle"', COLORS.yellow)}: {`,
|
|
7151
|
-
` ${colorize('"command"', COLORS.yellow)}: ${colorize('"muggle"', COLORS.green)},`,
|
|
7152
|
-
` ${colorize('"args"', COLORS.yellow)}: [${colorize('"serve"', COLORS.green)}]`,
|
|
7153
|
-
` }`,
|
|
7154
|
-
` }`,
|
|
7155
|
-
` ${colorize("}", COLORS.dim)}`,
|
|
7156
|
-
"",
|
|
7157
|
-
` ${colorize("Step 2:", COLORS.bold)} Restart your MCP client`,
|
|
7158
|
-
"",
|
|
7159
|
-
` ${colorize("Step 3:", COLORS.bold)} Start testing! Ask your AI assistant:`,
|
|
7160
|
-
` ${colorize('"Test the login flow on my app at http://localhost:3000"', COLORS.dim)}`,
|
|
7161
|
-
"",
|
|
7162
|
-
header("CLI Commands"),
|
|
7163
|
-
"",
|
|
7164
|
-
` ${colorize("Server Commands:", COLORS.bold)}`,
|
|
7165
|
-
` ${cmd("muggle serve")} Start MCP server with all tools`,
|
|
7166
|
-
` ${cmd("muggle serve --e2e")} Start with cloud E2E tools only`,
|
|
7167
|
-
` ${cmd("muggle serve --local")} Start with local E2E tools only`,
|
|
7168
|
-
"",
|
|
7169
|
-
` ${colorize("Setup & Diagnostics:", COLORS.bold)}`,
|
|
7170
|
-
` ${cmd("muggle setup")} Download/update Electron app`,
|
|
7171
|
-
` ${cmd("muggle setup --force")} Force re-download`,
|
|
7172
|
-
` ${cmd("muggle doctor")} Diagnose installation issues`,
|
|
7173
|
-
` ${cmd("muggle upgrade")} Check for updates`,
|
|
7174
|
-
` ${cmd("muggle upgrade --check")} Check updates without installing`,
|
|
7175
|
-
"",
|
|
7176
|
-
` ${colorize("Authentication:", COLORS.bold)}`,
|
|
7177
|
-
` ${cmd("muggle login")} Login to Muggle AI`,
|
|
7178
|
-
` ${cmd("muggle logout")} Clear stored credentials`,
|
|
7179
|
-
` ${cmd("muggle status")} Show authentication status`,
|
|
7180
|
-
"",
|
|
7181
|
-
` ${colorize("Maintenance:", COLORS.bold)}`,
|
|
7182
|
-
` ${cmd("muggle versions")} List installed Electron app versions`,
|
|
7183
|
-
` ${cmd("muggle cleanup")} Remove old Electron app versions`,
|
|
7184
|
-
` ${cmd("muggle cleanup --all")} Remove all old versions`,
|
|
7185
|
-
` ${cmd("muggle cleanup --skills")} Also remove obsolete skills`,
|
|
7186
|
-
"",
|
|
7187
|
-
` ${colorize("Help:", COLORS.bold)}`,
|
|
7188
|
-
` ${cmd("muggle help")} Show this guide`,
|
|
7189
|
-
` ${cmd("muggle --help")} Show command synopsis`,
|
|
7190
|
-
` ${cmd("muggle --version")} Show version`,
|
|
7191
|
-
"",
|
|
7192
|
-
header("Authentication Flow"),
|
|
7193
|
-
"",
|
|
7194
|
-
" Authentication happens automatically when you first use a tool that",
|
|
7195
|
-
" requires it:",
|
|
7196
|
-
"",
|
|
7197
|
-
` 1. ${colorize("A browser window opens", COLORS.bold)} with a verification code`,
|
|
7198
|
-
` 2. ${colorize("Log in", COLORS.bold)} with your Muggle AI account`,
|
|
7199
|
-
` 3. ${colorize("The tool call continues", COLORS.bold)} with your credentials`,
|
|
7200
|
-
"",
|
|
7201
|
-
` API keys are stored in ${path12("~/.muggle-ai/api-key.json")}`,
|
|
7202
|
-
"",
|
|
7203
|
-
header("Available MCP Tools"),
|
|
7204
|
-
"",
|
|
7205
|
-
` ${colorize("Cloud E2E tools:", COLORS.bold)} (prefix: muggle-remote-)`,
|
|
7206
|
-
" muggle-remote-project-create, muggle-remote-project-list, muggle-remote-use-case-create-from-prompts,",
|
|
7207
|
-
" muggle-remote-test-case-generate-from-prompt, muggle-remote-workflow-start-*, etc.",
|
|
7208
|
-
"",
|
|
7209
|
-
` ${colorize("Local E2E tools:", COLORS.bold)} (prefix: muggle-local-)`,
|
|
7210
|
-
" muggle-local-execute-test-generation, muggle-local-execute-replay,",
|
|
7211
|
-
" muggle-local-publish-test-script, muggle-local-run-result-get,",
|
|
7212
|
-
" muggle-local-check-status, etc.",
|
|
7213
|
-
"",
|
|
7214
|
-
header("Data Directory"),
|
|
7215
|
-
"",
|
|
7216
|
-
` All data is stored in ${path12("~/.muggle-ai/")}:`,
|
|
7217
|
-
"",
|
|
7218
|
-
` ${path12("api-key.json")} Long-lived API key (auto-generated)`,
|
|
7219
|
-
` ${path12("projects/")} Local test projects`,
|
|
7220
|
-
` ${path12("sessions/")} Test execution sessions`,
|
|
7221
|
-
` ${path12("electron-app/")} Downloaded Electron app binaries`,
|
|
7222
|
-
"",
|
|
7223
|
-
header("Troubleshooting"),
|
|
7224
|
-
"",
|
|
7225
|
-
` ${colorize("Installation issues:", COLORS.bold)}`,
|
|
7226
|
-
` Run ${cmd("muggle doctor")} to diagnose problems`,
|
|
7227
|
-
"",
|
|
7228
|
-
` ${colorize("Electron app not found:", COLORS.bold)}`,
|
|
7229
|
-
` Run ${cmd("muggle setup --force")} to re-download`,
|
|
7230
|
-
"",
|
|
7231
|
-
` ${colorize("Authentication issues:", COLORS.bold)}`,
|
|
7232
|
-
` Run ${cmd("muggle logout")} then ${cmd("muggle login")}`,
|
|
7233
|
-
"",
|
|
7234
|
-
` ${colorize("MCP not working in client:", COLORS.bold)}`,
|
|
7235
|
-
" 1. Verify mcp.json configuration",
|
|
7236
|
-
" 2. Restart your MCP client",
|
|
7237
|
-
` 3. Check ${cmd("muggle doctor")} output`,
|
|
7238
|
-
"",
|
|
7239
|
-
header("Documentation & Support"),
|
|
7240
|
-
"",
|
|
7241
|
-
` Docs: ${colorize("https://www.muggle-ai.com/muggleTestV0/docs/mcp/mcp-overview", COLORS.blue)}`,
|
|
7242
|
-
` GitHub: ${colorize("https://github.com/multiplex-ai/muggle-ai-works", COLORS.blue)}`,
|
|
7243
|
-
"",
|
|
7244
|
-
colorize("=".repeat(70), COLORS.cyan),
|
|
7245
|
-
""
|
|
7246
|
-
];
|
|
7247
|
-
return lines.join("\n");
|
|
7248
|
-
}
|
|
7249
|
-
function helpCommand() {
|
|
7250
|
-
console.log(getHelpGuidance());
|
|
7251
|
-
}
|
|
7252
|
-
|
|
7253
|
-
// src/cli/login.ts
|
|
7254
|
-
var logger9 = getLogger();
|
|
7255
|
-
async function loginCommand(options) {
|
|
7256
|
-
console.log("\nMuggle AI Login");
|
|
7257
|
-
console.log("===============\n");
|
|
7258
|
-
const expiry = options.keyExpiry || "90d";
|
|
7259
|
-
console.log("Starting device code authentication...");
|
|
7260
|
-
console.log("A browser window will open for you to complete login.\n");
|
|
7261
|
-
const result = await performLogin(options.keyName, expiry);
|
|
7262
|
-
if (result.success) {
|
|
7263
|
-
console.log("\u2713 Login successful!");
|
|
7264
|
-
if (result.credentials?.email) {
|
|
7265
|
-
console.log(` Logged in as: ${result.credentials.email}`);
|
|
7266
|
-
}
|
|
7267
|
-
if (result.credentials?.apiKey) {
|
|
7268
|
-
console.log(" API key created and stored for future use.");
|
|
7269
|
-
}
|
|
7270
|
-
console.log("\nYou can now use Muggle AI Works tools.");
|
|
7271
|
-
} else {
|
|
7272
|
-
console.error("\u2717 Login failed");
|
|
7273
|
-
if (result.error) {
|
|
7274
|
-
console.error(` Error: ${result.error}`);
|
|
7275
|
-
}
|
|
7276
|
-
if (result.deviceCodeResponse) {
|
|
7277
|
-
console.log("\nIf browser didn't open, visit:");
|
|
7278
|
-
console.log(` ${result.deviceCodeResponse.verificationUriComplete}`);
|
|
7279
|
-
console.log(` Code: ${result.deviceCodeResponse.userCode}`);
|
|
7280
|
-
}
|
|
7281
|
-
process.exit(1);
|
|
7282
|
-
}
|
|
7283
|
-
}
|
|
7284
|
-
async function logoutCommand() {
|
|
7285
|
-
console.log("\nLogging out...");
|
|
7286
|
-
performLogout();
|
|
7287
|
-
console.log("\u2713 Credentials cleared successfully.");
|
|
7288
|
-
logger9.info("Logout completed");
|
|
7289
|
-
}
|
|
7290
|
-
async function statusCommand() {
|
|
7291
|
-
console.log("\nAuthentication Status");
|
|
7292
|
-
console.log("=====================\n");
|
|
7293
|
-
const authService = getAuthService();
|
|
7294
|
-
const status = authService.getAuthStatus();
|
|
7295
|
-
const hasStoredApiKey = hasApiKey();
|
|
7296
|
-
if (status.authenticated) {
|
|
7297
|
-
console.log("\u2713 Authenticated");
|
|
7298
|
-
if (status.email) {
|
|
7299
|
-
console.log(` Email: ${status.email}`);
|
|
7300
|
-
}
|
|
7301
|
-
if (status.userId) {
|
|
7302
|
-
console.log(` User ID: ${status.userId}`);
|
|
7303
|
-
}
|
|
7304
|
-
if (status.expiresAt) {
|
|
7305
|
-
const expiresDate = new Date(status.expiresAt);
|
|
7306
|
-
console.log(` Token expires: ${expiresDate.toLocaleString()}`);
|
|
7307
|
-
if (status.isExpired) {
|
|
7308
|
-
console.log(" (Token expired - will refresh automatically on next API call)");
|
|
7309
|
-
}
|
|
7310
|
-
}
|
|
7311
|
-
console.log(` API Key: ${hasStoredApiKey ? "Yes" : "No"}`);
|
|
7312
|
-
} else {
|
|
7313
|
-
console.log("\u2717 Not authenticated");
|
|
7314
|
-
console.log("\nRun 'muggle login' to authenticate.");
|
|
7315
|
-
}
|
|
7316
|
-
}
|
|
7317
|
-
|
|
7318
|
-
// src/cli/serve.ts
|
|
7319
|
-
var logger10 = getLogger();
|
|
7320
|
-
async function serveCommand(options) {
|
|
7321
|
-
const config = getConfig();
|
|
7322
|
-
const enableQa = options.local ? false : true;
|
|
7323
|
-
const enableLocal = options.e2e ? false : true;
|
|
7324
|
-
logger10.info("Starting Muggle MCP Server", {
|
|
7325
|
-
version: config.serverVersion,
|
|
7326
|
-
enableQa,
|
|
7327
|
-
enableLocal,
|
|
7328
|
-
transport: "stdio"
|
|
7329
|
-
});
|
|
7330
|
-
try {
|
|
7331
|
-
if (enableQa) {
|
|
7332
|
-
const qaTools = getQaTools();
|
|
7333
|
-
registerTools(qaTools);
|
|
7334
|
-
logger10.info("Registered cloud E2E acceptance tools", { count: qaTools.length });
|
|
7335
|
-
}
|
|
7336
|
-
if (enableLocal) {
|
|
7337
|
-
const localTools = getLocalQaTools();
|
|
7338
|
-
registerTools(localTools);
|
|
7339
|
-
logger10.info("Registered local E2E acceptance tools", { count: localTools.length });
|
|
7340
|
-
}
|
|
7341
|
-
const mcpServer = createUnifiedMcpServer({
|
|
7342
|
-
enableQaTools: enableQa,
|
|
7343
|
-
enableLocalTools: enableLocal
|
|
7344
|
-
});
|
|
7345
|
-
await startStdioServer(mcpServer);
|
|
7346
|
-
logger10.info("MCP server started successfully");
|
|
7347
|
-
} catch (error) {
|
|
7348
|
-
logger10.error("Failed to start MCP server", {
|
|
7349
|
-
error: error instanceof Error ? error.message : String(error),
|
|
7350
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
7351
|
-
});
|
|
7352
|
-
process.exit(1);
|
|
7353
|
-
}
|
|
7354
|
-
}
|
|
7355
|
-
var logger11 = getLogger();
|
|
7356
|
-
var MAX_RETRY_ATTEMPTS = 3;
|
|
7357
|
-
var RETRY_BASE_DELAY_MS = 2e3;
|
|
7358
|
-
var INSTALL_METADATA_FILE_NAME = ".install-metadata.json";
|
|
7359
|
-
function getBinaryName() {
|
|
7360
|
-
const os4 = platform();
|
|
7361
|
-
const architecture = arch();
|
|
7362
|
-
switch (os4) {
|
|
7363
|
-
case "darwin": {
|
|
7364
|
-
const darwinArch = architecture === "arm64" ? "arm64" : "x64";
|
|
7365
|
-
return `MuggleAI-darwin-${darwinArch}.zip`;
|
|
7366
|
-
}
|
|
7367
|
-
case "win32":
|
|
7368
|
-
return "MuggleAI-win32-x64.zip";
|
|
7369
|
-
case "linux":
|
|
7370
|
-
return "MuggleAI-linux-x64.zip";
|
|
7371
|
-
default:
|
|
7372
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
7373
|
-
}
|
|
7374
|
-
}
|
|
7375
|
-
function getExpectedExecutablePath2(versionDir) {
|
|
7376
|
-
const os4 = platform();
|
|
7377
|
-
switch (os4) {
|
|
7378
|
-
case "darwin":
|
|
7379
|
-
return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
7380
|
-
case "win32":
|
|
7381
|
-
return path2.join(versionDir, "MuggleAI.exe");
|
|
7382
|
-
case "linux":
|
|
7383
|
-
return path2.join(versionDir, "MuggleAI");
|
|
7384
|
-
default:
|
|
7385
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
7386
|
-
}
|
|
7387
|
-
}
|
|
7388
|
-
async function extractZip(zipPath, destDir) {
|
|
7389
|
-
return new Promise((resolve4, reject) => {
|
|
7390
|
-
if (platform() === "win32") {
|
|
7391
|
-
execFile(
|
|
7392
|
-
"powershell",
|
|
7393
|
-
["-command", `Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force`],
|
|
7394
|
-
(error) => {
|
|
7395
|
-
if (error) {
|
|
7396
|
-
reject(error);
|
|
7397
|
-
} else {
|
|
7398
|
-
resolve4();
|
|
7399
|
-
}
|
|
7400
|
-
}
|
|
7401
|
-
);
|
|
7402
|
-
} else {
|
|
7403
|
-
execFile("unzip", ["-o", zipPath, "-d", destDir], (error) => {
|
|
7404
|
-
if (error) {
|
|
7405
|
-
reject(error);
|
|
7406
|
-
} else {
|
|
7407
|
-
resolve4();
|
|
7408
|
-
}
|
|
7409
|
-
});
|
|
7410
|
-
}
|
|
7411
|
-
});
|
|
7412
|
-
}
|
|
7413
|
-
async function extractTarGz(tarPath, destDir) {
|
|
7414
|
-
return new Promise((resolve4, reject) => {
|
|
7415
|
-
execFile("tar", ["-xzf", tarPath, "-C", destDir], (error) => {
|
|
7416
|
-
if (error) {
|
|
7417
|
-
reject(error);
|
|
7418
|
-
} else {
|
|
7419
|
-
resolve4();
|
|
7420
|
-
}
|
|
7421
|
-
});
|
|
7422
|
-
});
|
|
7423
|
-
}
|
|
7424
|
-
function sleep2(ms) {
|
|
7425
|
-
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
7426
|
-
}
|
|
7427
|
-
async function downloadWithRetry(downloadUrl, destPath) {
|
|
7428
|
-
let lastError = null;
|
|
7429
|
-
for (let attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
|
|
7430
|
-
try {
|
|
7431
|
-
if (attempt > 1) {
|
|
7432
|
-
const delayMs = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 2);
|
|
7433
|
-
console.log(`Retry attempt ${attempt}/${MAX_RETRY_ATTEMPTS} after ${delayMs}ms delay...`);
|
|
7434
|
-
await sleep2(delayMs);
|
|
7435
|
-
}
|
|
7436
|
-
const response = await fetch(downloadUrl);
|
|
7437
|
-
if (!response.ok) {
|
|
7438
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
7439
|
-
}
|
|
7440
|
-
if (!response.body) {
|
|
7441
|
-
throw new Error("No response body received");
|
|
7442
|
-
}
|
|
7443
|
-
const fileStream = createWriteStream(destPath);
|
|
7444
|
-
await pipeline(response.body, fileStream);
|
|
7445
|
-
return true;
|
|
7446
|
-
} catch (error) {
|
|
7447
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
7448
|
-
console.error(`Download attempt ${attempt} failed: ${lastError.message}`);
|
|
7449
|
-
if (existsSync(destPath)) {
|
|
7450
|
-
rmSync(destPath, { force: true });
|
|
7451
|
-
}
|
|
7452
|
-
}
|
|
7453
|
-
}
|
|
7454
|
-
if (lastError) {
|
|
7455
|
-
throw new Error(`Download failed after ${MAX_RETRY_ATTEMPTS} attempts: ${lastError.message}`);
|
|
7456
|
-
}
|
|
7457
|
-
return false;
|
|
7458
|
-
}
|
|
7459
|
-
function writeInstallMetadata(params) {
|
|
7460
|
-
const metadata = {
|
|
7461
|
-
version: params.version,
|
|
7462
|
-
binaryName: params.binaryName,
|
|
7463
|
-
platformKey: params.platformKey,
|
|
7464
|
-
executableChecksum: params.executableChecksum,
|
|
7465
|
-
expectedArchiveChecksum: params.expectedArchiveChecksum,
|
|
7466
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7467
|
-
};
|
|
7468
|
-
writeFileSync(params.metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
7469
|
-
`, "utf-8");
|
|
7470
|
-
}
|
|
7471
|
-
function cleanupFailedInstall(versionDir) {
|
|
7472
|
-
if (existsSync(versionDir)) {
|
|
7473
|
-
try {
|
|
7474
|
-
rmSync(versionDir, { recursive: true, force: true });
|
|
7475
|
-
} catch {
|
|
7476
|
-
}
|
|
7477
|
-
}
|
|
7478
|
-
}
|
|
7479
|
-
async function setupCommand(options) {
|
|
7480
|
-
const version = getElectronAppVersion();
|
|
7481
|
-
const versionDir = getElectronAppDir(version);
|
|
7482
|
-
const platformKey = getPlatformKey();
|
|
7483
|
-
if (!options.force && isElectronAppInstalled()) {
|
|
7484
|
-
console.log(`Electron app v${version} is already installed at ${versionDir}`);
|
|
7485
|
-
console.log("Use --force to re-download.");
|
|
7486
|
-
return;
|
|
7487
|
-
}
|
|
7488
|
-
const binaryName = getBinaryName();
|
|
7489
|
-
const downloadUrl = buildElectronAppReleaseAssetUrl({
|
|
7490
|
-
version,
|
|
7491
|
-
assetFileName: binaryName
|
|
7492
|
-
});
|
|
7493
|
-
console.log(`Downloading Muggle Test Electron app v${version}...`);
|
|
7494
|
-
console.log(`URL: ${downloadUrl}`);
|
|
7495
|
-
try {
|
|
7496
|
-
if (existsSync(versionDir)) {
|
|
7497
|
-
rmSync(versionDir, { recursive: true, force: true });
|
|
7498
|
-
}
|
|
7499
|
-
mkdirSync(versionDir, { recursive: true });
|
|
7500
|
-
const tempFile = path2.join(versionDir, binaryName);
|
|
7501
|
-
await downloadWithRetry(downloadUrl, tempFile);
|
|
7502
|
-
console.log("Download complete, verifying checksum...");
|
|
7503
|
-
const checksums = getElectronAppChecksums();
|
|
7504
|
-
const expectedChecksum = getChecksumForPlatform(checksums);
|
|
7505
|
-
const checksumResult = await verifyFileChecksum(tempFile, expectedChecksum);
|
|
7506
|
-
if (!checksumResult.valid && expectedChecksum) {
|
|
7507
|
-
cleanupFailedInstall(versionDir);
|
|
7508
|
-
throw new Error(
|
|
7509
|
-
`Checksum verification failed!
|
|
7510
|
-
Expected: ${checksumResult.expected}
|
|
7511
|
-
Actual: ${checksumResult.actual}
|
|
7512
|
-
The downloaded file may be corrupted or tampered with.`
|
|
7513
|
-
);
|
|
7514
|
-
}
|
|
7515
|
-
if (expectedChecksum) {
|
|
7516
|
-
console.log("Checksum verified successfully.");
|
|
7517
|
-
} else {
|
|
7518
|
-
console.log("Warning: No checksum configured, skipping verification.");
|
|
7519
|
-
}
|
|
7520
|
-
console.log("Extracting...");
|
|
7521
|
-
if (binaryName.endsWith(".zip")) {
|
|
7522
|
-
await extractZip(tempFile, versionDir);
|
|
7523
|
-
} else if (binaryName.endsWith(".tar.gz")) {
|
|
7524
|
-
await extractTarGz(tempFile, versionDir);
|
|
7525
|
-
}
|
|
7526
|
-
const executablePath = getExpectedExecutablePath2(versionDir);
|
|
7527
|
-
if (!existsSync(executablePath)) {
|
|
7528
|
-
cleanupFailedInstall(versionDir);
|
|
7529
|
-
throw new Error(
|
|
7530
|
-
`Extraction failed: executable not found at expected path.
|
|
7531
|
-
Expected: ${executablePath}
|
|
7532
|
-
The archive may be corrupted or in an unexpected format.`
|
|
7533
|
-
);
|
|
7534
|
-
}
|
|
7535
|
-
const executableChecksum = await calculateFileChecksum(executablePath);
|
|
7536
|
-
const metadataPath = path2.join(versionDir, INSTALL_METADATA_FILE_NAME);
|
|
7537
|
-
writeInstallMetadata({
|
|
7538
|
-
metadataPath,
|
|
7539
|
-
version,
|
|
7540
|
-
binaryName,
|
|
7541
|
-
platformKey,
|
|
7542
|
-
executableChecksum,
|
|
7543
|
-
expectedArchiveChecksum: expectedChecksum
|
|
7544
|
-
});
|
|
7545
|
-
rmSync(tempFile, { force: true });
|
|
7546
|
-
console.log(`Electron app installed to ${versionDir}`);
|
|
7547
|
-
logger11.info("Setup complete", { version, path: versionDir });
|
|
7548
|
-
} catch (error) {
|
|
7549
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7550
|
-
console.error(`Failed to download Electron app: ${errorMessage}`);
|
|
7551
|
-
logger11.error("Setup failed", { error: errorMessage });
|
|
7552
|
-
process.exit(1);
|
|
7553
|
-
}
|
|
7554
|
-
}
|
|
7555
|
-
var logger12 = getLogger();
|
|
7556
|
-
var GITHUB_RELEASES_API = "https://api.github.com/repos/multiplex-ai/muggle-ai-works/releases";
|
|
7557
|
-
var INSTALL_METADATA_FILE_NAME2 = ".install-metadata.json";
|
|
7558
|
-
var VERSION_OVERRIDE_FILE2 = "electron-app-version-override.json";
|
|
7559
|
-
function getBinaryName2() {
|
|
7560
|
-
const os4 = platform();
|
|
7561
|
-
const arch3 = process.arch;
|
|
7562
|
-
switch (os4) {
|
|
7563
|
-
case "darwin":
|
|
7564
|
-
return arch3 === "arm64" ? "MuggleAI-darwin-arm64.zip" : "MuggleAI-darwin-x64.zip";
|
|
7565
|
-
case "win32":
|
|
7566
|
-
return "MuggleAI-win32-x64.zip";
|
|
7567
|
-
case "linux":
|
|
7568
|
-
return "MuggleAI-linux-x64.zip";
|
|
7569
|
-
default:
|
|
7570
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
7571
|
-
}
|
|
7572
|
-
}
|
|
7573
|
-
function extractVersionFromTag(tag) {
|
|
7574
|
-
const match = tag.match(/^(?:electron-app-)?v(\d+\.\d+\.\d+)$/);
|
|
7575
|
-
return match ? match[1] : null;
|
|
7576
|
-
}
|
|
7577
|
-
function getVersionOverridePath() {
|
|
7578
|
-
return path2.join(getDataDir2(), VERSION_OVERRIDE_FILE2);
|
|
7579
|
-
}
|
|
7580
|
-
function getEffectiveElectronAppVersion() {
|
|
7581
|
-
const overridePath = getVersionOverridePath();
|
|
7582
|
-
if (existsSync(overridePath)) {
|
|
7583
|
-
try {
|
|
7584
|
-
const content = JSON.parse(__require("fs").readFileSync(overridePath, "utf-8"));
|
|
7585
|
-
if (content.version) {
|
|
7586
|
-
return content.version;
|
|
7587
|
-
}
|
|
7588
|
-
} catch {
|
|
7589
|
-
}
|
|
7590
|
-
}
|
|
7591
|
-
return getElectronAppVersion();
|
|
7592
|
-
}
|
|
7593
|
-
function saveVersionOverride(version) {
|
|
7594
|
-
const overridePath = getVersionOverridePath();
|
|
7595
|
-
const dataDir = getDataDir2();
|
|
7596
|
-
if (!existsSync(dataDir)) {
|
|
7597
|
-
mkdirSync(dataDir, { recursive: true });
|
|
7598
|
-
}
|
|
7599
|
-
writeFileSync(overridePath, JSON.stringify({
|
|
7600
|
-
version,
|
|
7601
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7602
|
-
}, null, 2), "utf-8");
|
|
7603
|
-
}
|
|
7604
|
-
async function checkForUpdates() {
|
|
7605
|
-
const currentVersion = getEffectiveElectronAppVersion();
|
|
7606
|
-
try {
|
|
7607
|
-
const response = await fetch(GITHUB_RELEASES_API, {
|
|
7608
|
-
headers: {
|
|
7609
|
-
"Accept": "application/vnd.github.v3+json",
|
|
7610
|
-
"User-Agent": "muggle"
|
|
7611
|
-
}
|
|
7612
|
-
});
|
|
7613
|
-
if (!response.ok) {
|
|
7614
|
-
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
7615
|
-
}
|
|
7616
|
-
const releases = await response.json();
|
|
7617
|
-
for (const release2 of releases) {
|
|
7618
|
-
if (release2.prerelease || release2.draft) {
|
|
7619
|
-
continue;
|
|
7620
|
-
}
|
|
7621
|
-
const version = extractVersionFromTag(release2.tag_name);
|
|
7622
|
-
if (version) {
|
|
7623
|
-
const updateAvailable = compareVersions2(version, currentVersion) > 0;
|
|
7624
|
-
const binaryName = getBinaryName2();
|
|
7625
|
-
return {
|
|
7626
|
-
currentVersion,
|
|
7627
|
-
latestVersion: version,
|
|
7628
|
-
updateAvailable,
|
|
7629
|
-
downloadUrl: buildElectronAppReleaseAssetUrl({
|
|
7630
|
-
version,
|
|
7631
|
-
assetFileName: binaryName
|
|
7632
|
-
})
|
|
7633
|
-
};
|
|
7634
|
-
}
|
|
7635
|
-
}
|
|
7636
|
-
return {
|
|
7637
|
-
currentVersion,
|
|
7638
|
-
latestVersion: currentVersion,
|
|
7639
|
-
updateAvailable: false
|
|
7640
|
-
};
|
|
7641
|
-
} catch (error) {
|
|
7642
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7643
|
-
logger12.warn("Failed to check for updates", { error: errorMessage });
|
|
7644
|
-
throw new Error(`Failed to check for updates: ${errorMessage}`, { cause: error });
|
|
7645
|
-
}
|
|
7646
|
-
}
|
|
7647
|
-
function compareVersions2(a, b) {
|
|
7648
|
-
const partsA = a.split(".").map(Number);
|
|
7649
|
-
const partsB = b.split(".").map(Number);
|
|
7650
|
-
for (let i = 0; i < 3; i++) {
|
|
7651
|
-
const partA = partsA[i] || 0;
|
|
7652
|
-
const partB = partsB[i] || 0;
|
|
7653
|
-
if (partA > partB) {
|
|
7654
|
-
return 1;
|
|
7655
|
-
}
|
|
7656
|
-
if (partA < partB) {
|
|
7657
|
-
return -1;
|
|
7658
|
-
}
|
|
7659
|
-
}
|
|
7660
|
-
return 0;
|
|
7661
|
-
}
|
|
7662
|
-
function getExpectedExecutablePath3(versionDir) {
|
|
7663
|
-
const os4 = platform();
|
|
7664
|
-
switch (os4) {
|
|
7665
|
-
case "darwin":
|
|
7666
|
-
return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
7667
|
-
case "win32":
|
|
7668
|
-
return path2.join(versionDir, "MuggleAI.exe");
|
|
7669
|
-
case "linux":
|
|
7670
|
-
return path2.join(versionDir, "MuggleAI");
|
|
7671
|
-
default:
|
|
7672
|
-
throw new Error(`Unsupported platform: ${os4}`);
|
|
7673
|
-
}
|
|
7674
|
-
}
|
|
7675
|
-
function writeInstallMetadata2(params) {
|
|
7676
|
-
const metadata = {
|
|
7677
|
-
version: params.version,
|
|
7678
|
-
binaryName: params.binaryName,
|
|
7679
|
-
platformKey: params.platformKey,
|
|
7680
|
-
executableChecksum: params.executableChecksum,
|
|
7681
|
-
expectedArchiveChecksum: params.expectedArchiveChecksum,
|
|
7682
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7683
|
-
};
|
|
7684
|
-
writeFileSync(params.metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
7685
|
-
`, "utf-8");
|
|
7686
|
-
}
|
|
7687
|
-
async function extractZip2(zipPath, destDir) {
|
|
7688
|
-
return new Promise((resolve4, reject) => {
|
|
7689
|
-
if (platform() === "win32") {
|
|
7690
|
-
execFile(
|
|
7691
|
-
"powershell",
|
|
7692
|
-
["-command", `Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force`],
|
|
7693
|
-
(error) => {
|
|
7694
|
-
if (error) {
|
|
7695
|
-
reject(error);
|
|
7696
|
-
} else {
|
|
7697
|
-
resolve4();
|
|
7698
|
-
}
|
|
7699
|
-
}
|
|
7700
|
-
);
|
|
7701
|
-
} else {
|
|
7702
|
-
execFile("unzip", ["-o", zipPath, "-d", destDir], (error) => {
|
|
7703
|
-
if (error) {
|
|
7704
|
-
reject(error);
|
|
7705
|
-
} else {
|
|
7706
|
-
resolve4();
|
|
7707
|
-
}
|
|
7708
|
-
});
|
|
7709
|
-
}
|
|
7710
|
-
});
|
|
7711
|
-
}
|
|
7712
|
-
async function extractTarGz2(tarPath, destDir) {
|
|
7713
|
-
return new Promise((resolve4, reject) => {
|
|
7714
|
-
execFile("tar", ["-xzf", tarPath, "-C", destDir], (error) => {
|
|
7715
|
-
if (error) {
|
|
7716
|
-
reject(error);
|
|
7717
|
-
} else {
|
|
7718
|
-
resolve4();
|
|
7719
|
-
}
|
|
7720
|
-
});
|
|
7721
|
-
});
|
|
7722
|
-
}
|
|
7723
|
-
async function fetchChecksumFromRelease(version) {
|
|
7724
|
-
const checksumUrl = buildElectronAppChecksumsUrl(version);
|
|
7725
|
-
try {
|
|
7726
|
-
const response = await fetch(checksumUrl);
|
|
7727
|
-
if (!response.ok) {
|
|
7728
|
-
logger12.warn("Checksums file not found in release", { version });
|
|
7729
|
-
return "";
|
|
7730
|
-
}
|
|
7731
|
-
const text = await response.text();
|
|
7732
|
-
const binaryName = getBinaryName2();
|
|
7733
|
-
const platformKey = getPlatformKey();
|
|
7734
|
-
const lines = text.split("\n");
|
|
7735
|
-
for (const line of lines) {
|
|
7736
|
-
const trimmed = line.trim();
|
|
7737
|
-
if (!trimmed) {
|
|
7738
|
-
continue;
|
|
7739
|
-
}
|
|
7740
|
-
const match = trimmed.match(/^([a-fA-F0-9]{64})\s+(.+)$/);
|
|
7741
|
-
if (match) {
|
|
7742
|
-
const checksum = match[1];
|
|
7743
|
-
const filename = match[2];
|
|
7744
|
-
if (filename === binaryName || filename.includes(platformKey)) {
|
|
7745
|
-
logger12.info("Found checksum in release", {
|
|
7746
|
-
version,
|
|
7747
|
-
platform: platformKey,
|
|
7748
|
-
checksum: checksum.substring(0, 16) + "..."
|
|
7749
|
-
});
|
|
7750
|
-
return checksum;
|
|
7751
|
-
}
|
|
7752
|
-
}
|
|
7753
|
-
}
|
|
7754
|
-
logger12.warn("Platform checksum not found in checksums.txt", {
|
|
7755
|
-
version,
|
|
7756
|
-
platform: platformKey
|
|
7757
|
-
});
|
|
7758
|
-
return "";
|
|
7759
|
-
} catch (error) {
|
|
7760
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7761
|
-
logger12.warn("Failed to fetch checksums from release", {
|
|
7762
|
-
version,
|
|
7763
|
-
error: errorMessage
|
|
7764
|
-
});
|
|
7765
|
-
return "";
|
|
7766
|
-
}
|
|
7767
|
-
}
|
|
7768
|
-
async function downloadAndInstall(version, downloadUrl, checksum) {
|
|
7769
|
-
const versionDir = getElectronAppDir(version);
|
|
7770
|
-
const binaryName = getBinaryName2();
|
|
7771
|
-
const platformKey = getPlatformKey();
|
|
7772
|
-
console.log(`Downloading Muggle Test Electron app v${version}...`);
|
|
7773
|
-
console.log(`URL: ${downloadUrl}`);
|
|
7774
|
-
if (existsSync(versionDir)) {
|
|
7775
|
-
rmSync(versionDir, { recursive: true, force: true });
|
|
7776
|
-
}
|
|
7777
|
-
mkdirSync(versionDir, { recursive: true });
|
|
7778
|
-
const response = await fetch(downloadUrl);
|
|
7779
|
-
if (!response.ok) {
|
|
7780
|
-
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
7781
|
-
}
|
|
7782
|
-
const tempFile = path2.join(versionDir, binaryName);
|
|
7783
|
-
const fileStream = createWriteStream(tempFile);
|
|
7784
|
-
if (!response.body) {
|
|
7785
|
-
throw new Error("No response body");
|
|
7786
|
-
}
|
|
7787
|
-
await pipeline(response.body, fileStream);
|
|
7788
|
-
console.log("Download complete, verifying checksum...");
|
|
7789
|
-
let expectedChecksum = checksum;
|
|
7790
|
-
if (!expectedChecksum) {
|
|
7791
|
-
expectedChecksum = await fetchChecksumFromRelease(version);
|
|
7792
|
-
}
|
|
7793
|
-
const checksumResult = await verifyFileChecksum(tempFile, expectedChecksum || "");
|
|
7794
|
-
if (!checksumResult.valid && expectedChecksum) {
|
|
7795
|
-
rmSync(versionDir, { recursive: true, force: true });
|
|
7796
|
-
throw new Error(
|
|
7797
|
-
`Checksum verification failed!
|
|
7798
|
-
Expected: ${checksumResult.expected}
|
|
7799
|
-
Actual: ${checksumResult.actual}
|
|
7800
|
-
The downloaded file may be corrupted or tampered with.`
|
|
7801
|
-
);
|
|
7802
|
-
}
|
|
7803
|
-
if (expectedChecksum) {
|
|
7804
|
-
console.log("Checksum verified successfully.");
|
|
7805
|
-
} else {
|
|
7806
|
-
console.log("Warning: No checksum available, skipping verification.");
|
|
7807
|
-
}
|
|
7808
|
-
console.log("Extracting...");
|
|
7809
|
-
if (binaryName.endsWith(".zip")) {
|
|
7810
|
-
await extractZip2(tempFile, versionDir);
|
|
7811
|
-
} else if (binaryName.endsWith(".tar.gz")) {
|
|
7812
|
-
await extractTarGz2(tempFile, versionDir);
|
|
7813
|
-
}
|
|
7814
|
-
const executablePath = getExpectedExecutablePath3(versionDir);
|
|
7815
|
-
if (!existsSync(executablePath)) {
|
|
7816
|
-
rmSync(versionDir, { recursive: true, force: true });
|
|
7817
|
-
throw new Error(
|
|
7818
|
-
`Extraction failed: executable not found at expected path.
|
|
7819
|
-
Expected: ${executablePath}
|
|
7820
|
-
The archive may be corrupted or in an unexpected format.`
|
|
7821
|
-
);
|
|
7822
|
-
}
|
|
7823
|
-
const executableChecksum = await calculateFileChecksum(executablePath);
|
|
7824
|
-
const metadataPath = path2.join(versionDir, INSTALL_METADATA_FILE_NAME2);
|
|
7825
|
-
writeInstallMetadata2({
|
|
7826
|
-
metadataPath,
|
|
7827
|
-
version,
|
|
7828
|
-
binaryName,
|
|
7829
|
-
platformKey,
|
|
7830
|
-
executableChecksum,
|
|
7831
|
-
expectedArchiveChecksum: expectedChecksum || ""
|
|
7832
|
-
});
|
|
7833
|
-
rmSync(tempFile, { force: true });
|
|
7834
|
-
saveVersionOverride(version);
|
|
7835
|
-
console.log(`Electron app v${version} installed to ${versionDir}`);
|
|
7836
|
-
logger12.info("Upgrade complete", { version, path: versionDir });
|
|
7837
|
-
}
|
|
7838
|
-
async function upgradeCommand(options) {
|
|
7839
|
-
try {
|
|
7840
|
-
if (options.version) {
|
|
7841
|
-
const binaryName = getBinaryName2();
|
|
7842
|
-
const downloadUrl = buildElectronAppReleaseAssetUrl({
|
|
7843
|
-
version: options.version,
|
|
7844
|
-
assetFileName: binaryName
|
|
7845
|
-
});
|
|
7846
|
-
await downloadAndInstall(options.version, downloadUrl);
|
|
7847
|
-
const cleanupResult2 = cleanupOldVersions({ all: false });
|
|
7848
|
-
if (cleanupResult2.removed.length > 0) {
|
|
7849
|
-
console.log(
|
|
7850
|
-
`
|
|
7851
|
-
Cleaned up ${cleanupResult2.removed.length} old version(s), freed ${formatBytes(cleanupResult2.freedBytes)}`
|
|
7852
|
-
);
|
|
7853
|
-
}
|
|
7854
|
-
return;
|
|
7855
|
-
}
|
|
7856
|
-
console.log("Checking for updates...");
|
|
7857
|
-
const result = await checkForUpdates();
|
|
7858
|
-
console.log(`Current version: ${result.currentVersion}`);
|
|
7859
|
-
console.log(`Latest version: ${result.latestVersion}`);
|
|
7860
|
-
if (options.check) {
|
|
7861
|
-
if (result.updateAvailable) {
|
|
7862
|
-
console.log("\nUpdate available! Run 'muggle upgrade' to install.");
|
|
7863
|
-
} else {
|
|
7864
|
-
console.log("\nYou are on the latest version.");
|
|
7865
|
-
}
|
|
7866
|
-
return;
|
|
7867
|
-
}
|
|
7868
|
-
if (!result.updateAvailable && !options.force) {
|
|
7869
|
-
console.log("\nYou are already on the latest version.");
|
|
7870
|
-
console.log("Use --force to re-download the current version.");
|
|
7871
|
-
return;
|
|
7872
|
-
}
|
|
7873
|
-
if (!result.downloadUrl) {
|
|
7874
|
-
throw new Error("No download URL available");
|
|
7875
|
-
}
|
|
7876
|
-
await downloadAndInstall(result.latestVersion, result.downloadUrl);
|
|
7877
|
-
const cleanupResult = cleanupOldVersions({ all: false });
|
|
7878
|
-
if (cleanupResult.removed.length > 0) {
|
|
7879
|
-
console.log(
|
|
7880
|
-
`
|
|
7881
|
-
Cleaned up ${cleanupResult.removed.length} old version(s), freed ${formatBytes(cleanupResult.freedBytes)}`
|
|
7882
|
-
);
|
|
7883
|
-
}
|
|
7884
|
-
} catch (error) {
|
|
7885
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7886
|
-
console.error(`Upgrade failed: ${errorMessage}`);
|
|
7887
|
-
logger12.error("Upgrade failed", { error: errorMessage });
|
|
7888
|
-
process.exit(1);
|
|
7889
|
-
}
|
|
7890
|
-
}
|
|
7891
|
-
|
|
7892
|
-
// packages/commands/src/cli/run-cli.ts
|
|
7893
|
-
var __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
7894
|
-
function getPackageRoot2() {
|
|
7895
|
-
if (__dirname$1.endsWith("dist")) {
|
|
7896
|
-
return resolve(__dirname$1, "..");
|
|
7897
|
-
}
|
|
7898
|
-
return resolve(__dirname$1, "..", "..", "..", "..");
|
|
7899
|
-
}
|
|
7900
|
-
var packageVersion = JSON.parse(
|
|
7901
|
-
readFileSync(resolve(getPackageRoot2(), "package.json"), "utf-8")
|
|
7902
|
-
).version;
|
|
7903
|
-
var logger13 = getLogger();
|
|
7904
|
-
function createProgram() {
|
|
7905
|
-
const program = new Command();
|
|
7906
|
-
program.name("muggle").description("Unified MCP server for Muggle AI \u2014 cloud E2E and local E2E testing").version(packageVersion);
|
|
7907
|
-
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);
|
|
7908
|
-
program.command("setup").description("Download/update the Electron app for local testing").option("--force", "Force re-download even if already installed").action(setupCommand);
|
|
7909
|
-
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);
|
|
7910
|
-
program.command("versions").description("List installed electron-app versions").action(versionsCommand);
|
|
7911
|
-
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);
|
|
7912
|
-
program.command("doctor").description("Diagnose installation and configuration issues").action(doctorCommand);
|
|
7913
|
-
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);
|
|
7914
|
-
program.command("logout").description("Clear stored credentials").action(logoutCommand);
|
|
7915
|
-
program.command("status").description("Show authentication status").action(statusCommand);
|
|
7916
|
-
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);
|
|
7917
|
-
program.action(() => {
|
|
7918
|
-
helpCommand();
|
|
7919
|
-
});
|
|
7920
|
-
program.on("command:*", () => {
|
|
7921
|
-
helpCommand();
|
|
7922
|
-
process.exit(1);
|
|
7923
|
-
});
|
|
7924
|
-
return program;
|
|
7925
|
-
}
|
|
7926
|
-
function handleHelpCommand() {
|
|
7927
|
-
const args = process.argv.slice(2);
|
|
7928
|
-
if (args.length === 1 && args[0] === "help") {
|
|
7929
|
-
helpCommand();
|
|
7930
|
-
return true;
|
|
7931
|
-
}
|
|
7932
|
-
return false;
|
|
7933
|
-
}
|
|
7934
|
-
async function runCli() {
|
|
7935
|
-
try {
|
|
7936
|
-
if (handleHelpCommand()) {
|
|
7937
|
-
return;
|
|
7938
|
-
}
|
|
7939
|
-
const program = createProgram();
|
|
7940
|
-
await program.parseAsync(process.argv);
|
|
7941
|
-
} catch (error) {
|
|
7942
|
-
logger13.error("CLI error", {
|
|
7943
|
-
error: error instanceof Error ? error.message : String(error)
|
|
7944
|
-
});
|
|
7945
|
-
process.exit(1);
|
|
7946
|
-
}
|
|
7947
|
-
}
|
|
7948
|
-
|
|
7949
|
-
// packages/commands/src/index.ts
|
|
7950
|
-
var src_exports2 = {};
|
|
7951
|
-
__export(src_exports2, {
|
|
7952
|
-
cleanupCommand: () => cleanupCommand,
|
|
7953
|
-
doctorCommand: () => doctorCommand,
|
|
7954
|
-
helpCommand: () => helpCommand,
|
|
7955
|
-
loginCommand: () => loginCommand,
|
|
7956
|
-
logoutCommand: () => logoutCommand,
|
|
7957
|
-
registerCoreCommands: () => registerCoreCommands,
|
|
7958
|
-
runCli: () => runCli,
|
|
7959
|
-
serveCommand: () => serveCommand,
|
|
7960
|
-
setupCommand: () => setupCommand,
|
|
7961
|
-
statusCommand: () => statusCommand,
|
|
7962
|
-
upgradeCommand: () => upgradeCommand,
|
|
7963
|
-
versionsCommand: () => versionsCommand
|
|
7964
|
-
});
|
|
7965
|
-
|
|
7966
|
-
// packages/commands/src/registry/register-core-commands.ts
|
|
7967
|
-
function registerCoreCommands(commandRegistrationContext) {
|
|
7968
|
-
}
|
|
7969
|
-
|
|
7970
|
-
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 };
|