@muggleai/works 5.19.0-staging.131 → 5.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- import * as fs6 from 'fs';
1
+ import * as fs4 from 'fs';
2
2
  import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
3
- import * as path11 from 'path';
3
+ import * as path13 from 'path';
4
4
  import { join, dirname } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import * as crypto from 'crypto';
@@ -10,7 +10,7 @@ import { platform } from 'os';
10
10
  import winston from 'winston';
11
11
  import { exec, spawn } from 'child_process';
12
12
  import axios, { AxiosError } from 'axios';
13
- import * as fs10 from 'fs/promises';
13
+ import * as fs12 from 'fs/promises';
14
14
  import AppInsights from 'applicationinsights';
15
15
  import { z } from 'zod';
16
16
 
@@ -29,8 +29,10 @@ var __export = (target, all) => {
29
29
  // packages/mcps/src/index.ts
30
30
  var src_exports = {};
31
31
  __export(src_exports, {
32
+ ACTION_SCRIPT_FILE_NAME: () => ACTION_SCRIPT_FILE_NAME,
32
33
  DEFAULT_PREFERENCES: () => DEFAULT_PREFERENCES,
33
34
  ElectronAppReleaseStream: () => ElectronAppReleaseStream,
35
+ HTML_VIEW_OFFER_LINE: () => HTML_VIEW_OFFER_LINE,
34
36
  ONBOARDING_BLANKET_OPTIONS: () => ONBOARDING_BLANKET_OPTIONS,
35
37
  ONBOARDING_CHOICE_GROUPS: () => ONBOARDING_CHOICE_GROUPS,
36
38
  ONBOARDING_MAX_OFFERS: () => ONBOARDING_MAX_OFFERS,
@@ -49,7 +51,11 @@ __export(src_exports, {
49
51
  PreferenceKey: () => PreferenceKey,
50
52
  PreferenceValue: () => PreferenceValue,
51
53
  ProjectPreferencesReconcileOutcome: () => ProjectPreferencesReconcileOutcome,
54
+ RUN_SESSIONS_DIR_NAME: () => RUN_SESSIONS_DIR_NAME,
55
+ RunViewError: () => RunViewError,
52
56
  RuntimeTarget: () => RuntimeTarget,
57
+ SCREENSHOT_DIR_RELATIVE_PATH: () => SCREENSHOT_DIR_RELATIVE_PATH,
58
+ SCREENSHOT_FILE_EXTENSION: () => SCREENSHOT_FILE_EXTENSION,
53
59
  WATCHER_LIFETIME_SECONDS: () => WATCHER_LIFETIME_SECONDS,
54
60
  WATCHER_LIFETIME_UNBOUNDED_SECONDS: () => WATCHER_LIFETIME_UNBOUNDED_SECONDS,
55
61
  applyOnboardingAnswers: () => applyOnboardingAnswers,
@@ -91,6 +97,7 @@ __export(src_exports, {
91
97
  getPlatformKey: () => getPlatformKey,
92
98
  getQaTools: () => getQaTools,
93
99
  getReleaseSignerIdentityUri: () => getReleaseSignerIdentityUri,
100
+ getRunSessionsDir: () => getRunSessionsDir,
94
101
  getValidApiKeyData: () => getValidApiKeyData,
95
102
  getValidCredentials: () => getValidCredentials,
96
103
  hasApiKey: () => hasApiKey,
@@ -108,8 +115,11 @@ __export(src_exports, {
108
115
  pollDeviceCode: () => pollDeviceCode,
109
116
  qa: () => e2e_exports2,
110
117
  readPreferencesMetadata: () => readPreferencesMetadata,
118
+ readRunView: () => readRunView,
111
119
  reconcileProjectPreferences: () => reconcileProjectPreferences,
112
120
  recordOnboardingSkip: () => recordOnboardingSkip,
121
+ renderRunViewHtml: () => renderRunViewHtml,
122
+ renderRunViewText: () => renderRunViewText,
113
123
  resetConfig: () => resetConfig,
114
124
  resetLogger: () => resetLogger,
115
125
  resetPreference: () => resetPreference,
@@ -120,6 +130,7 @@ __export(src_exports, {
120
130
  resolveElectronAppPathOrNull: () => resolveElectronAppPathOrNull,
121
131
  resolveOnboardingAnswers: () => resolveOnboardingAnswers,
122
132
  resolvePreferences: () => resolvePreferences,
133
+ resolveRunSessionPath: () => resolveRunSessionPath,
123
134
  resolveRuntimeTarget: () => resolveRuntimeTarget,
124
135
  resolveTogglePreferences: () => resolveTogglePreferences,
125
136
  saveApiKey: () => saveApiKey,
@@ -130,7 +141,8 @@ __export(src_exports, {
130
141
  validatePreference: () => validatePreference,
131
142
  verifyFileChecksum: () => verifyFileChecksum,
132
143
  writePreferences: () => writePreferences2,
133
- writePreferencesWithMetadata: () => writePreferencesWithMetadata
144
+ writePreferencesWithMetadata: () => writePreferencesWithMetadata,
145
+ writeRunViewHtml: () => writeRunViewHtml
134
146
  });
135
147
 
136
148
  // packages/mcps/src/shared/runtime-target-types.ts
@@ -148,29 +160,29 @@ var ElectronAppReleaseStream = /* @__PURE__ */ ((ElectronAppReleaseStream3) => {
148
160
  var DEFAULT_AUTH0_SCOPE = "openid profile email offline_access";
149
161
  var DEFAULT_WEB_SERVICE_URL = "http://localhost:3001";
150
162
  var RUNTIME_TARGET_ENV_VAR = "MUGGLE_MCP_PROMPT_SERVICE_TARGET";
151
- var RUNTIME_TARGETS_FILE = path11.join("config", "runtime-targets.json");
163
+ var RUNTIME_TARGETS_FILE = path13.join("config", "runtime-targets.json");
152
164
  function getPackageRoot() {
153
- const currentDir = path11.dirname(fileURLToPath(import.meta.url));
154
- if (currentDir.includes(path11.join("dist", "shared"))) {
155
- return path11.resolve(currentDir, "..", "..");
165
+ const currentDir = path13.dirname(fileURLToPath(import.meta.url));
166
+ if (currentDir.includes(path13.join("dist", "shared"))) {
167
+ return path13.resolve(currentDir, "..", "..");
156
168
  }
157
169
  if (currentDir.endsWith("dist")) {
158
- return path11.resolve(currentDir, "..");
170
+ return path13.resolve(currentDir, "..");
159
171
  }
160
- if (currentDir.includes(path11.join("src", "shared"))) {
161
- return path11.resolve(currentDir, "..", "..", "..", "..");
172
+ if (currentDir.includes(path13.join("src", "shared"))) {
173
+ return path13.resolve(currentDir, "..", "..", "..", "..");
162
174
  }
163
- return path11.dirname(currentDir);
175
+ return path13.dirname(currentDir);
164
176
  }
165
177
  var runtimeTargetDataCache = null;
166
178
  function getRuntimeTargetData() {
167
179
  if (runtimeTargetDataCache) {
168
180
  return runtimeTargetDataCache;
169
181
  }
170
- const profilesPath = path11.join(getPackageRoot(), RUNTIME_TARGETS_FILE);
182
+ const profilesPath = path13.join(getPackageRoot(), RUNTIME_TARGETS_FILE);
171
183
  let parsedData;
172
184
  try {
173
- parsedData = JSON.parse(fs6.readFileSync(profilesPath, "utf-8"));
185
+ parsedData = JSON.parse(fs4.readFileSync(profilesPath, "utf-8"));
174
186
  } catch (error) {
175
187
  const errorMessage = error instanceof Error ? error.message : String(error);
176
188
  throw new Error(
@@ -246,7 +258,7 @@ function assertDeviceCodeClientProvisioned(target) {
246
258
  }
247
259
  var DATA_DIR_NAME = ".muggle-ai";
248
260
  function getDataDir() {
249
- return path11.join(os4.homedir(), DATA_DIR_NAME);
261
+ return path13.join(os4.homedir(), DATA_DIR_NAME);
250
262
  }
251
263
  var DEV_MANIFEST = {
252
264
  release: "dev",
@@ -300,27 +312,27 @@ var configInstance = null;
300
312
  var muggleConfigCache = null;
301
313
  function getPackageRoot2() {
302
314
  const currentFilePath = fileURLToPath(import.meta.url);
303
- const currentDir = path11.dirname(currentFilePath);
304
- if (currentDir.includes(path11.join("dist", "shared"))) {
305
- return path11.resolve(currentDir, "..", "..");
315
+ const currentDir = path13.dirname(currentFilePath);
316
+ if (currentDir.includes(path13.join("dist", "shared"))) {
317
+ return path13.resolve(currentDir, "..", "..");
306
318
  }
307
319
  if (currentDir.endsWith("dist")) {
308
- return path11.resolve(currentDir, "..");
320
+ return path13.resolve(currentDir, "..");
309
321
  }
310
- if (currentDir.includes(path11.join("src", "shared"))) {
311
- return path11.resolve(currentDir, "..", "..", "..", "..");
322
+ if (currentDir.includes(path13.join("src", "shared"))) {
323
+ return path13.resolve(currentDir, "..", "..", "..", "..");
312
324
  }
313
- return path11.dirname(currentDir);
325
+ return path13.dirname(currentDir);
314
326
  }
315
327
  function getMuggleConfig() {
316
328
  if (muggleConfigCache) {
317
329
  return muggleConfigCache;
318
330
  }
319
331
  const packageRoot = getPackageRoot2();
320
- const packageJsonPath = path11.join(packageRoot, "package.json");
332
+ const packageJsonPath = path13.join(packageRoot, "package.json");
321
333
  let packageJson;
322
334
  try {
323
- const content = fs6.readFileSync(packageJsonPath, "utf-8");
335
+ const content = fs4.readFileSync(packageJsonPath, "utf-8");
324
336
  packageJson = JSON.parse(content);
325
337
  } catch (error) {
326
338
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -393,7 +405,7 @@ function buildOAuthSessionFileName(params) {
393
405
  function getDownloadedElectronAppPath() {
394
406
  const platformName = os4.platform();
395
407
  const version = getElectronAppVersion();
396
- const baseDir = path11.join(
408
+ const baseDir = path13.join(
397
409
  getDataDir2(),
398
410
  ELECTRON_APP_DIR,
399
411
  buildElectronAppDirName({
@@ -404,18 +416,18 @@ function getDownloadedElectronAppPath() {
404
416
  let binaryPath;
405
417
  switch (platformName) {
406
418
  case "darwin":
407
- binaryPath = path11.join(baseDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
419
+ binaryPath = path13.join(baseDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
408
420
  break;
409
421
  case "win32":
410
- binaryPath = path11.join(baseDir, "MuggleAI.exe");
422
+ binaryPath = path13.join(baseDir, "MuggleAI.exe");
411
423
  break;
412
424
  case "linux":
413
- binaryPath = path11.join(baseDir, "MuggleAI");
425
+ binaryPath = path13.join(baseDir, "MuggleAI");
414
426
  break;
415
427
  default:
416
428
  return null;
417
429
  }
418
- if (fs6.existsSync(binaryPath)) {
430
+ if (fs4.existsSync(binaryPath)) {
419
431
  return binaryPath;
420
432
  }
421
433
  return null;
@@ -426,25 +438,25 @@ function getSystemElectronAppPath() {
426
438
  let binaryPath;
427
439
  switch (platformName) {
428
440
  case "darwin":
429
- binaryPath = path11.join(homeDir, "Applications", "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
441
+ binaryPath = path13.join(homeDir, "Applications", "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
430
442
  break;
431
443
  case "win32":
432
- binaryPath = path11.join(homeDir, "AppData", "Local", "Programs", "MuggleAI", "MuggleAI.exe");
444
+ binaryPath = path13.join(homeDir, "AppData", "Local", "Programs", "MuggleAI", "MuggleAI.exe");
433
445
  break;
434
446
  case "linux":
435
- binaryPath = path11.join(homeDir, ".local", "share", "muggle-ai", "MuggleAI");
447
+ binaryPath = path13.join(homeDir, ".local", "share", "muggle-ai", "MuggleAI");
436
448
  break;
437
449
  default:
438
450
  return null;
439
451
  }
440
- if (fs6.existsSync(binaryPath)) {
452
+ if (fs4.existsSync(binaryPath)) {
441
453
  return binaryPath;
442
454
  }
443
455
  return null;
444
456
  }
445
457
  function resolveElectronAppPathOrNull() {
446
458
  const customPath = process.env.ELECTRON_APP_PATH;
447
- if (customPath && fs6.existsSync(customPath)) {
459
+ if (customPath && fs4.existsSync(customPath)) {
448
460
  return customPath;
449
461
  }
450
462
  const downloadedPath = getDownloadedElectronAppPath();
@@ -459,12 +471,12 @@ function resolveElectronAppPathOrNull() {
459
471
  }
460
472
  function resolveWebServicePath() {
461
473
  const customPath = process.env.WEB_SERVICE_PATH;
462
- if (customPath && fs6.existsSync(customPath)) {
474
+ if (customPath && fs4.existsSync(customPath)) {
463
475
  return customPath;
464
476
  }
465
477
  const packageRoot = getPackageRoot2();
466
- const siblingPath = path11.resolve(packageRoot, "..", "web-service", "dist", "src", "index.js");
467
- if (fs6.existsSync(siblingPath)) {
478
+ const siblingPath = path13.resolve(packageRoot, "..", "web-service", "dist", "src", "index.js");
479
+ if (fs4.existsSync(siblingPath)) {
468
480
  return siblingPath;
469
481
  }
470
482
  return null;
@@ -506,16 +518,16 @@ function buildLocalQaConfig() {
506
518
  webServiceUrl: process.env.WEB_SERVICE_URL ?? DEFAULT_WEB_SERVICE_URL,
507
519
  promptServiceUrl: process.env.PROMPT_SERVICE_BASE_URL ?? activeProfile.promptServiceBaseUrl,
508
520
  dataDir,
509
- sessionsDir: path11.join(dataDir, "sessions"),
510
- projectsDir: path11.join(dataDir, "projects"),
511
- tempDir: path11.join(dataDir, "temp"),
512
- apiKeyFilePath: path11.join(dataDir, API_KEY_FILE),
513
- oauthSessionFilePath: path11.join(
521
+ sessionsDir: path13.join(dataDir, "sessions"),
522
+ projectsDir: path13.join(dataDir, "projects"),
523
+ tempDir: path13.join(dataDir, "temp"),
524
+ apiKeyFilePath: path13.join(dataDir, API_KEY_FILE),
525
+ oauthSessionFilePath: path13.join(
514
526
  dataDir,
515
527
  buildOAuthSessionFileName({ runtimeTarget: getActiveRuntimeTarget() })
516
528
  ),
517
529
  webServicePath: resolveWebServicePath(),
518
- webServicePidFile: path11.join(dataDir, "web-service.pid"),
530
+ webServicePidFile: path13.join(dataDir, "web-service.pid"),
519
531
  auth0: {
520
532
  domain: process.env.AUTH0_DOMAIN ?? activeProfile.auth0Domain,
521
533
  clientId: process.env.AUTH0_CLIENT_ID ?? activeProfile.auth0ClientId,
@@ -550,10 +562,10 @@ function getElectronAppVersion() {
550
562
  if (envVersion && /^\d+\.\d+\.\d+$/.test(envVersion)) {
551
563
  return envVersion;
552
564
  }
553
- const overridePath = path11.join(getDataDir2(), VERSION_OVERRIDE_FILE);
554
- if (fs6.existsSync(overridePath)) {
565
+ const overridePath = path13.join(getDataDir2(), VERSION_OVERRIDE_FILE);
566
+ if (fs4.existsSync(overridePath)) {
555
567
  try {
556
- const content = JSON.parse(fs6.readFileSync(overridePath, "utf-8"));
568
+ const content = JSON.parse(fs4.readFileSync(overridePath, "utf-8"));
557
569
  if (content.version && typeof content.version === "string") {
558
570
  return content.version;
559
571
  }
@@ -567,10 +579,10 @@ function getElectronAppVersionSource() {
567
579
  if (envVersion && /^\d+\.\d+\.\d+$/.test(envVersion)) {
568
580
  return "env";
569
581
  }
570
- const overridePath = path11.join(getDataDir2(), VERSION_OVERRIDE_FILE);
571
- if (fs6.existsSync(overridePath)) {
582
+ const overridePath = path13.join(getDataDir2(), VERSION_OVERRIDE_FILE);
583
+ if (fs4.existsSync(overridePath)) {
572
584
  try {
573
- const content = JSON.parse(fs6.readFileSync(overridePath, "utf-8"));
585
+ const content = JSON.parse(fs4.readFileSync(overridePath, "utf-8"));
574
586
  if (content.version && typeof content.version === "string") {
575
587
  return "override";
576
588
  }
@@ -614,7 +626,7 @@ function isElectronAppInstalled() {
614
626
  }
615
627
  function getElectronAppDir(version) {
616
628
  const resolvedVersion = version ?? getElectronAppVersion();
617
- return path11.join(
629
+ return path13.join(
618
630
  getDataDir2(),
619
631
  ELECTRON_APP_DIR,
620
632
  buildElectronAppDirName({
@@ -680,7 +692,7 @@ function getPlatformKey() {
680
692
  async function calculateFileChecksum(filePath) {
681
693
  return new Promise((resolve6, reject) => {
682
694
  const hash = crypto.createHash("sha256");
683
- const stream = fs6.createReadStream(filePath);
695
+ const stream = fs4.createReadStream(filePath);
684
696
  stream.on("data", (data) => {
685
697
  hash.update(data);
686
698
  });
@@ -695,7 +707,7 @@ async function calculateFileChecksum(filePath) {
695
707
  async function verifyFileChecksum(filePath, expectedChecksum) {
696
708
  if (!expectedChecksum || expectedChecksum.trim() === "") {
697
709
  logger.warn("Checksum verification skipped - no checksum provided", {
698
- file: path11.basename(filePath)
710
+ file: path13.basename(filePath)
699
711
  });
700
712
  return {
701
713
  valid: true,
@@ -711,13 +723,13 @@ async function verifyFileChecksum(filePath, expectedChecksum) {
711
723
  const valid = normalizedExpected === normalizedActual;
712
724
  if (!valid) {
713
725
  logger.error("Checksum verification failed", {
714
- file: path11.basename(filePath),
726
+ file: path13.basename(filePath),
715
727
  expected: normalizedExpected,
716
728
  actual: normalizedActual
717
729
  });
718
730
  } else {
719
731
  logger.info("Checksum verified successfully", {
720
- file: path11.basename(filePath),
732
+ file: path13.basename(filePath),
721
733
  checksum: normalizedActual
722
734
  });
723
735
  }
@@ -730,7 +742,7 @@ async function verifyFileChecksum(filePath, expectedChecksum) {
730
742
  } catch (error) {
731
743
  const errorMessage = error instanceof Error ? error.message : String(error);
732
744
  logger.error("Checksum calculation failed", {
733
- file: path11.basename(filePath),
745
+ file: path13.basename(filePath),
734
746
  error: errorMessage
735
747
  });
736
748
  return {
@@ -795,107 +807,13 @@ async function openBrowserUrl(options) {
795
807
  }
796
808
  });
797
809
  }
798
- var API_KEY_FILE2 = "api-key.json";
799
- function getApiKeyFilePath() {
800
- return path11.join(getDataDir(), API_KEY_FILE2);
801
- }
802
- function ensureDataDir() {
803
- const dataDir = getDataDir();
804
- if (!fs6.existsSync(dataDir)) {
805
- fs6.mkdirSync(dataDir, { recursive: true });
806
- }
807
- }
808
- function loadApiKeyData() {
809
- const logger6 = getLogger();
810
- const apiKeyPath = getApiKeyFilePath();
811
- try {
812
- if (!fs6.existsSync(apiKeyPath)) {
813
- logger6.debug("No API key file found", { path: apiKeyPath });
814
- return null;
815
- }
816
- const content = fs6.readFileSync(apiKeyPath, "utf-8");
817
- const data = JSON.parse(content);
818
- return data;
819
- } catch (error) {
820
- logger6.warn("Failed to load API key data", {
821
- error: error instanceof Error ? error.message : String(error)
822
- });
823
- return null;
824
- }
825
- }
826
- function saveApiKeyData(data) {
827
- const logger6 = getLogger();
828
- const apiKeyPath = getApiKeyFilePath();
829
- try {
830
- ensureDataDir();
831
- const content = JSON.stringify(data, null, 2);
832
- fs6.writeFileSync(apiKeyPath, content, { mode: 384 });
833
- logger6.info("API key saved", { path: apiKeyPath });
834
- } catch (error) {
835
- logger6.error("Failed to save API key", {
836
- error: error instanceof Error ? error.message : String(error)
837
- });
838
- throw error;
839
- }
840
- }
841
- function deleteApiKeyData() {
842
- const logger6 = getLogger();
843
- const apiKeyPath = getApiKeyFilePath();
844
- try {
845
- if (fs6.existsSync(apiKeyPath)) {
846
- fs6.unlinkSync(apiKeyPath);
847
- logger6.info("API key deleted", { path: apiKeyPath });
848
- }
849
- } catch (error) {
850
- logger6.warn("Failed to delete API key", {
851
- error: error instanceof Error ? error.message : String(error)
852
- });
853
- }
854
- }
855
- function getValidApiKeyData() {
856
- const data = loadApiKeyData();
857
- if (!data) {
858
- return null;
859
- }
860
- if (data.apiKey) {
861
- return data;
862
- }
863
- return null;
864
- }
865
- function hasApiKey() {
866
- const data = loadApiKeyData();
867
- return !!data?.apiKey;
868
- }
869
- function getApiKey() {
870
- const data = loadApiKeyData();
871
- return data?.apiKey ?? null;
872
- }
873
- function saveApiKey(params) {
874
- const logger6 = getLogger();
875
- const apiKeyPath = getApiKeyFilePath();
876
- try {
877
- ensureDataDir();
878
- const data = {
879
- accessToken: "",
880
- expiresAt: "",
881
- apiKey: params.apiKey,
882
- apiKeyId: params.apiKeyId
883
- };
884
- const content = JSON.stringify(data, null, 2);
885
- fs6.writeFileSync(apiKeyPath, content, { mode: 384 });
886
- logger6.info("API key saved", { path: apiKeyPath });
887
- } catch (error) {
888
- logger6.error("Failed to save API key", {
889
- error: error instanceof Error ? error.message : String(error)
890
- });
891
- throw error;
892
- }
893
- }
894
- var loadCredentials = loadApiKeyData;
895
- var saveCredentials = saveApiKeyData;
896
- var deleteCredentials = deleteApiKeyData;
897
- var getValidCredentials = getValidApiKeyData;
898
- var getCredentialsFilePath = getApiKeyFilePath;
810
+
811
+ // packages/mcps/src/mcp/local/services/run-view/run-view-constants.ts
812
+ var RUN_SESSIONS_DIR_NAME = "sessions";
813
+ var ACTION_SCRIPT_FILE_NAME = "action-script.json";
814
+ var SCREENSHOT_DIR_RELATIVE_PATH = "electron-runtime/screenshot";
815
+ var SCREENSHOT_FILE_EXTENSION = ".jpg";
816
+ var HTML_VIEW_OFFER_LINE = "See the frames: re-run with --html (add --open to open it).";
899
817
 
900
818
  // packages/mcps/src/mcp/local/types/enums.ts
901
819
  var DeviceCodePollStatus = /* @__PURE__ */ ((DeviceCodePollStatus2) => {
@@ -980,1214 +898,1513 @@ var DisplayResolution = /* @__PURE__ */ ((DisplayResolution2) => {
980
898
  return DisplayResolution2;
981
899
  })(DisplayResolution || {});
982
900
 
983
- // packages/mcps/src/mcp/local/services/stored-auth-target.ts
984
- function decodeJwtPayload(token) {
985
- try {
986
- const parts = token.split(".");
987
- if (parts.length !== 3) {
988
- return null;
989
- }
990
- const payload = parts[1];
991
- const decoded = Buffer.from(payload, "base64url").toString("utf-8");
992
- return JSON.parse(decoded);
993
- } catch {
994
- return null;
995
- }
996
- }
997
- function extractJwtIssuer(token) {
998
- const payload = decodeJwtPayload(token);
999
- if (!payload || typeof payload.iss !== "string") {
1000
- return null;
1001
- }
1002
- return payload.iss;
1003
- }
1004
- function normalizeIssuerHost(issuer) {
1005
- return issuer.replace(/^https:\/\//, "").replace(/\/$/, "");
1006
- }
1007
- function isStoredAuthForRuntimeTarget(params) {
1008
- if (params.tokenIssuer && params.expectedAuth0Domain) {
1009
- const issuerHost = normalizeIssuerHost(params.tokenIssuer);
1010
- const expectedHost = normalizeIssuerHost(params.expectedAuth0Domain);
1011
- if (issuerHost !== expectedHost) {
1012
- return false;
1013
- }
1014
- }
1015
- if (!params.storedRuntimeTarget) {
1016
- return params.activeRuntimeTarget === "production" /* Production */;
1017
- }
1018
- return params.storedRuntimeTarget === params.activeRuntimeTarget;
1019
- }
1020
-
1021
- // packages/mcps/src/mcp/local/services/auth-service.ts
1022
- var DEFAULT_LOGIN_WAIT_TIMEOUT_MS = 12e4;
1023
- var AuthService = class {
1024
- /** Path to the OAuth session file. */
1025
- oauthSessionFilePath;
1026
- /** Path to the pending device code file. */
1027
- pendingDeviceCodePath;
901
+ // packages/mcps/src/mcp/local/services/storage-service.ts
902
+ var DEFAULT_SESSION_MAX_AGE_DAYS = 30;
903
+ var StorageService = class {
904
+ /** Base data directory. */
905
+ dataDir;
906
+ /** Sessions directory. */
907
+ sessionsDir;
1028
908
  /**
1029
- * Create a new AuthService.
909
+ * Create a new StorageService.
1030
910
  */
1031
911
  constructor() {
1032
912
  const config = getConfig();
1033
- this.oauthSessionFilePath = config.localQa.oauthSessionFilePath;
1034
- this.pendingDeviceCodePath = path11.join(
1035
- path11.dirname(config.localQa.oauthSessionFilePath),
1036
- "pending-device-code.json"
1037
- );
913
+ this.dataDir = config.localQa.dataDir;
914
+ this.sessionsDir = config.localQa.sessionsDir;
1038
915
  }
1039
916
  /**
1040
- * Get current authentication status.
917
+ * Ensure the base directories exist.
1041
918
  */
1042
- getAuthStatus() {
919
+ ensureDirectories() {
1043
920
  const logger6 = getLogger();
1044
- const storedAuth = this.loadStoredAuth();
1045
- if (!storedAuth) {
1046
- logger6.debug("No stored auth found");
1047
- return { authenticated: false };
921
+ if (!fs4.existsSync(this.dataDir)) {
922
+ fs4.mkdirSync(this.dataDir, { recursive: true });
923
+ logger6.info("Created data directory", { path: this.dataDir });
924
+ }
925
+ if (!fs4.existsSync(this.sessionsDir)) {
926
+ fs4.mkdirSync(this.sessionsDir, { recursive: true });
927
+ logger6.info("Created sessions directory", { path: this.sessionsDir });
1048
928
  }
1049
- const now = /* @__PURE__ */ new Date();
1050
- const expiresAt = new Date(storedAuth.expiresAt);
1051
- const isExpired = now >= expiresAt;
1052
- logger6.debug("Auth status checked", {
1053
- email: storedAuth.email,
1054
- isExpired,
1055
- expiresAt: storedAuth.expiresAt
1056
- });
1057
- return {
1058
- authenticated: !isExpired,
1059
- email: storedAuth.email,
1060
- userId: storedAuth.userId,
1061
- expiresAt: storedAuth.expiresAt,
1062
- isExpired
1063
- };
1064
929
  }
1065
930
  /**
1066
- * Start the device code flow.
1067
- * @param options.forceNewSession - Clear existing Auth0 browser session before login to allow account switching.
931
+ * Create a new session directory.
932
+ * @param sessionId - Unique session ID.
933
+ * @returns Path to the session directory.
1068
934
  */
1069
- async startDeviceCodeFlow(options) {
935
+ createSessionDirectory(sessionId) {
1070
936
  const logger6 = getLogger();
1071
- const config = getConfig();
1072
- const { domain, clientId, audience, scopes } = config.localQa.auth0;
1073
- logger6.info("Starting device code flow");
1074
- const url = `https://${domain}/oauth/device/code`;
1075
- const body = new URLSearchParams({
1076
- client_id: clientId,
1077
- scope: scopes.join(" "),
1078
- audience
1079
- });
1080
- const response = await fetch(url, {
1081
- method: "POST",
1082
- headers: {
1083
- "Content-Type": "application/x-www-form-urlencoded"
1084
- },
1085
- body: body.toString()
1086
- });
1087
- if (!response.ok) {
1088
- const errorText = await response.text();
1089
- logger6.error("Device code request failed", {
1090
- status: response.status,
1091
- error: errorText
1092
- });
1093
- throw new Error(`Failed to start device code flow: ${response.status} ${errorText}`);
1094
- }
1095
- const deviceCodeResponse = await response.json();
1096
- logger6.info("Device code flow started", {
1097
- userCode: deviceCodeResponse.user_code,
1098
- verificationUri: deviceCodeResponse.verification_uri,
1099
- expiresIn: deviceCodeResponse.expires_in
1100
- });
1101
- this.storePendingDeviceCode({
1102
- deviceCode: deviceCodeResponse.device_code,
1103
- userCode: deviceCodeResponse.user_code,
1104
- expiresAt: new Date(Date.now() + deviceCodeResponse.expires_in * 1e3).toISOString()
1105
- });
1106
- let browserUrl = deviceCodeResponse.verification_uri_complete;
1107
- if (options?.forceNewSession) {
1108
- this.logout();
1109
- const logoutUrl = new URL(`https://${domain}/v2/logout`);
1110
- logoutUrl.searchParams.set("client_id", clientId);
1111
- logoutUrl.searchParams.set("returnTo", deviceCodeResponse.verification_uri_complete);
1112
- browserUrl = logoutUrl.toString();
1113
- logger6.info("Force new session: cleared local token, opening logout-redirect URL", {
1114
- logoutUrl: browserUrl
1115
- });
1116
- }
1117
- const browserOpenResult = await openBrowserUrl({
1118
- url: browserUrl
1119
- });
1120
- if (browserOpenResult.opened) {
1121
- logger6.info("Browser opened for device code login");
1122
- } else {
1123
- logger6.warn("Failed to open browser for device code login", {
1124
- error: browserOpenResult.error,
1125
- url: browserUrl
1126
- });
937
+ this.ensureDirectories();
938
+ const sessionDir = path13.join(this.sessionsDir, sessionId);
939
+ if (!fs4.existsSync(sessionDir)) {
940
+ fs4.mkdirSync(sessionDir, { recursive: true });
941
+ fs4.mkdirSync(path13.join(sessionDir, "screenshots"), { recursive: true });
942
+ fs4.mkdirSync(path13.join(sessionDir, "logs"), { recursive: true });
943
+ logger6.info("Created session directory", { sessionId, path: sessionDir });
1127
944
  }
1128
- return {
1129
- deviceCode: deviceCodeResponse.device_code,
1130
- userCode: deviceCodeResponse.user_code,
1131
- verificationUri: deviceCodeResponse.verification_uri,
1132
- verificationUriComplete: deviceCodeResponse.verification_uri_complete,
1133
- expiresIn: deviceCodeResponse.expires_in,
1134
- interval: deviceCodeResponse.interval,
1135
- browserOpened: browserOpenResult.opened,
1136
- browserOpenError: browserOpenResult.error
1137
- };
945
+ return sessionDir;
1138
946
  }
1139
947
  /**
1140
- * Store a pending device code for later retrieval.
948
+ * Save session metadata.
949
+ * @param metadata - Session metadata to save.
1141
950
  */
1142
- storePendingDeviceCode(params) {
951
+ saveSessionMetadata(metadata) {
1143
952
  const logger6 = getLogger();
1144
- const dir = path11.dirname(this.pendingDeviceCodePath);
1145
- if (!fs6.existsSync(dir)) {
1146
- fs6.mkdirSync(dir, { recursive: true });
1147
- }
1148
- fs6.writeFileSync(this.pendingDeviceCodePath, JSON.stringify(params, null, 2), {
1149
- encoding: "utf-8",
1150
- mode: 384
1151
- });
1152
- logger6.debug("Pending device code stored", { userCode: params.userCode });
953
+ const sessionDir = this.createSessionDirectory(metadata.sessionId);
954
+ const metadataPath = path13.join(sessionDir, "metadata.json");
955
+ fs4.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
956
+ logger6.debug("Saved session metadata", { sessionId: metadata.sessionId });
1153
957
  }
1154
958
  /**
1155
- * Get the pending device code if one exists and is not expired.
959
+ * Load session metadata.
960
+ * @param sessionId - Session ID to load.
961
+ * @returns Session metadata, or null if not found.
1156
962
  */
1157
- getPendingDeviceCode() {
963
+ loadSessionMetadata(sessionId) {
1158
964
  const logger6 = getLogger();
1159
- if (!fs6.existsSync(this.pendingDeviceCodePath)) {
1160
- logger6.debug("No pending device code found");
965
+ const metadataPath = path13.join(this.sessionsDir, sessionId, "metadata.json");
966
+ if (!fs4.existsSync(metadataPath)) {
1161
967
  return null;
1162
968
  }
1163
969
  try {
1164
- const content = fs6.readFileSync(this.pendingDeviceCodePath, "utf-8");
1165
- const pendingDeviceCodeData = JSON.parse(content);
1166
- const now = /* @__PURE__ */ new Date();
1167
- const expiresAt = new Date(pendingDeviceCodeData.expiresAt);
1168
- if (now >= expiresAt) {
1169
- logger6.debug("Pending device code expired");
1170
- this.clearPendingDeviceCode();
1171
- return null;
1172
- }
1173
- return pendingDeviceCodeData.deviceCode;
970
+ const content = fs4.readFileSync(metadataPath, "utf-8");
971
+ return JSON.parse(content);
1174
972
  } catch (error) {
1175
- logger6.warn("Failed to read pending device code", {
973
+ logger6.error("Failed to load session metadata", {
974
+ sessionId,
1176
975
  error: error instanceof Error ? error.message : String(error)
1177
976
  });
1178
977
  return null;
1179
978
  }
1180
979
  }
1181
980
  /**
1182
- * Clear the pending device code file.
981
+ * List all sessions.
982
+ * @returns Array of session IDs.
1183
983
  */
1184
- clearPendingDeviceCode() {
1185
- const logger6 = getLogger();
1186
- if (fs6.existsSync(this.pendingDeviceCodePath)) {
1187
- try {
1188
- fs6.unlinkSync(this.pendingDeviceCodePath);
1189
- logger6.debug("Pending device code cleared");
1190
- } catch (error) {
1191
- logger6.warn("Failed to clear pending device code", {
1192
- error: error instanceof Error ? error.message : String(error)
1193
- });
1194
- }
984
+ listSessions() {
985
+ if (!fs4.existsSync(this.sessionsDir)) {
986
+ return [];
1195
987
  }
988
+ return fs4.readdirSync(this.sessionsDir).filter((entry) => {
989
+ const entryPath = path13.join(this.sessionsDir, entry);
990
+ return fs4.statSync(entryPath).isDirectory();
991
+ });
1196
992
  }
1197
993
  /**
1198
- * Poll for device code authorization completion.
994
+ * Get the current session ID (if any).
995
+ * @returns Current session ID, or null.
1199
996
  */
1200
- async pollDeviceCode(deviceCode) {
1201
- const logger6 = getLogger();
1202
- const config = getConfig();
1203
- const { domain, clientId } = config.localQa.auth0;
1204
- logger6.debug("Polling for device code authorization");
1205
- const url = `https://${domain}/oauth/token`;
1206
- const body = new URLSearchParams({
1207
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
1208
- client_id: clientId,
1209
- device_code: deviceCode
1210
- });
997
+ getCurrentSessionId() {
998
+ const currentPath = path13.join(this.sessionsDir, "current");
999
+ if (!fs4.existsSync(currentPath)) {
1000
+ return null;
1001
+ }
1211
1002
  try {
1212
- const response = await fetch(url, {
1213
- method: "POST",
1214
- headers: {
1215
- "Content-Type": "application/x-www-form-urlencoded"
1216
- },
1217
- body: body.toString()
1218
- });
1219
- if (response.ok) {
1220
- const tokenData = await response.json();
1221
- const tokenResponse = {
1222
- accessToken: tokenData.access_token,
1223
- refreshToken: tokenData.refresh_token,
1224
- tokenType: tokenData.token_type,
1225
- expiresIn: tokenData.expires_in
1226
- };
1227
- const userInfo = await this.getUserInfo(tokenResponse.accessToken);
1228
- await this.storeAuth({
1229
- tokenResponse,
1230
- email: userInfo.email,
1231
- userId: userInfo.sub
1232
- });
1233
- this.clearPendingDeviceCode();
1234
- logger6.info("Device code authorization complete", { email: userInfo.email });
1235
- return {
1236
- status: "complete" /* Complete */,
1237
- message: "Authentication successful!",
1238
- email: userInfo.email
1239
- };
1240
- }
1241
- const errorData = await response.json();
1242
- if (errorData.error === "authorization_pending") {
1243
- logger6.debug("Authorization pending");
1244
- return {
1245
- status: "pending" /* Pending */,
1246
- message: "Waiting for user to complete authorization..."
1247
- };
1248
- }
1249
- if (errorData.error === "slow_down") {
1250
- logger6.debug("Polling too fast");
1251
- return {
1252
- status: "pending" /* Pending */,
1253
- message: "Polling too fast, slowing down..."
1254
- };
1255
- }
1256
- if (errorData.error === "expired_token") {
1257
- logger6.warn("Device code expired");
1258
- return {
1259
- status: "expired" /* Expired */,
1260
- message: "The authorization code has expired. Please start again."
1261
- };
1262
- }
1263
- if (errorData.error === "access_denied") {
1264
- logger6.warn("Access denied");
1265
- return {
1266
- status: "error" /* Error */,
1267
- message: "Access was denied by the user.",
1268
- error: errorData.error_description ?? errorData.error
1269
- };
1270
- }
1271
- logger6.error("Unexpected error during polling", { error: errorData });
1272
- return {
1273
- status: "error" /* Error */,
1274
- message: errorData.error_description ?? errorData.error,
1275
- error: errorData.error
1276
- };
1277
- } catch (error) {
1278
- const errorMessage = error instanceof Error ? error.message : String(error);
1279
- logger6.error("Poll request failed", { error: errorMessage });
1280
- return {
1281
- status: "error" /* Error */,
1282
- message: `Poll request failed: ${errorMessage}`,
1283
- error: errorMessage
1284
- };
1003
+ const target = fs4.readlinkSync(currentPath);
1004
+ return path13.basename(target);
1005
+ } catch {
1006
+ return null;
1285
1007
  }
1286
1008
  }
1287
1009
  /**
1288
- * Poll for device code authorization until completion or timeout.
1010
+ * Set the current session.
1011
+ * @param sessionId - Session ID to set as current.
1289
1012
  */
1290
- async waitForDeviceCodeAuthorization(params) {
1013
+ setCurrentSession(sessionId) {
1291
1014
  const logger6 = getLogger();
1292
- const timeoutMs = params.timeoutMs ?? DEFAULT_LOGIN_WAIT_TIMEOUT_MS;
1293
- const pollIntervalMs = Math.max(params.intervalSeconds, 1) * 1e3;
1294
- const startedAt = Date.now();
1295
- logger6.info("Waiting for device code authorization", {
1296
- timeoutMs,
1297
- pollIntervalMs
1298
- });
1299
- while (Date.now() - startedAt < timeoutMs) {
1300
- const pollResult = await this.pollDeviceCode(params.deviceCode);
1301
- if (pollResult.status !== "pending" /* Pending */) {
1302
- return pollResult;
1303
- }
1304
- const remainingMs = timeoutMs - (Date.now() - startedAt);
1305
- if (remainingMs <= 0) {
1306
- break;
1307
- }
1308
- await sleep({ durationMs: Math.min(pollIntervalMs, remainingMs) });
1015
+ const currentPath = path13.join(this.sessionsDir, "current");
1016
+ const targetPath = path13.join(this.sessionsDir, sessionId);
1017
+ if (fs4.existsSync(currentPath)) {
1018
+ fs4.unlinkSync(currentPath);
1309
1019
  }
1310
- return {
1311
- status: "pending" /* Pending */,
1312
- message: "Timed out waiting for browser login confirmation. Please finish login in your browser, then call `muggle_auth_poll` to continue."
1313
- };
1020
+ fs4.symlinkSync(targetPath, currentPath);
1021
+ logger6.info("Set current session", { sessionId });
1314
1022
  }
1315
1023
  /**
1316
- * Get user info from Auth0.
1024
+ * Save a screenshot to the session directory.
1025
+ * @param params - Screenshot save parameters.
1317
1026
  */
1318
- async getUserInfo(accessToken) {
1027
+ saveScreenshot(params) {
1028
+ const { sessionId, filename, data } = params;
1319
1029
  const logger6 = getLogger();
1320
- const config = getConfig();
1321
- const { domain } = config.localQa.auth0;
1322
- const url = `https://${domain}/userinfo`;
1323
- try {
1324
- const response = await fetch(url, {
1325
- method: "GET",
1326
- headers: {
1327
- Authorization: `Bearer ${accessToken}`
1328
- }
1329
- });
1330
- if (!response.ok) {
1331
- logger6.warn("Failed to get user info", { status: response.status });
1332
- return {};
1333
- }
1334
- const userInfoData = await response.json();
1335
- return userInfoData;
1336
- } catch (error) {
1337
- logger6.warn("User info request failed", {
1338
- error: error instanceof Error ? error.message : String(error)
1339
- });
1340
- return {};
1341
- }
1030
+ const sessionDir = this.createSessionDirectory(sessionId);
1031
+ const screenshotPath = path13.join(sessionDir, "screenshots", filename);
1032
+ fs4.writeFileSync(screenshotPath, data);
1033
+ logger6.debug("Saved screenshot", { sessionId, filename });
1034
+ return screenshotPath;
1342
1035
  }
1343
1036
  /**
1344
- * Store authentication tokens.
1037
+ * Append to the results markdown file.
1038
+ * @param params - Results append parameters.
1345
1039
  */
1346
- async storeAuth(params) {
1347
- const { tokenResponse, email, userId } = params;
1040
+ appendToResults(params) {
1041
+ const { sessionId, content } = params;
1348
1042
  const logger6 = getLogger();
1349
- const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1e3).toISOString();
1350
- const storedAuth = {
1351
- accessToken: tokenResponse.accessToken,
1352
- refreshToken: tokenResponse.refreshToken,
1353
- expiresAt,
1354
- email,
1355
- userId,
1356
- runtimeTarget: getActiveRuntimeTarget()
1357
- };
1358
- const dir = path11.dirname(this.oauthSessionFilePath);
1359
- if (!fs6.existsSync(dir)) {
1360
- fs6.mkdirSync(dir, { recursive: true });
1361
- }
1362
- fs6.writeFileSync(this.oauthSessionFilePath, JSON.stringify(storedAuth, null, 2), {
1363
- encoding: "utf-8",
1364
- mode: 384
1365
- });
1366
- logger6.info("Auth stored successfully", { email, expiresAt });
1043
+ const sessionDir = this.createSessionDirectory(sessionId);
1044
+ const resultsPath = path13.join(sessionDir, "results.md");
1045
+ fs4.appendFileSync(resultsPath, content + "\n", "utf-8");
1046
+ logger6.debug("Appended to results", { sessionId });
1367
1047
  }
1368
1048
  /**
1369
- * Load stored authentication for the active runtime target.
1370
- * @returns Stored auth, or null when it is absent, unreadable, or was issued for another target.
1049
+ * Get the results markdown content.
1050
+ * @param sessionId - Session ID.
1051
+ * @returns Results content, or null if not found.
1371
1052
  */
1372
- loadStoredAuth() {
1373
- const logger6 = getLogger();
1374
- if (!fs6.existsSync(this.oauthSessionFilePath)) {
1375
- return null;
1376
- }
1377
- try {
1378
- const content = fs6.readFileSync(this.oauthSessionFilePath, "utf-8");
1379
- const storedAuth = JSON.parse(content);
1380
- const activeRuntimeTarget = getActiveRuntimeTarget();
1381
- const config = getConfig();
1382
- const expectedAuth0Domain = config.localQa.auth0.domain;
1383
- const tokenIssuer = extractJwtIssuer(storedAuth.accessToken);
1384
- if (!isStoredAuthForRuntimeTarget({
1385
- storedRuntimeTarget: storedAuth.runtimeTarget,
1386
- activeRuntimeTarget,
1387
- tokenIssuer,
1388
- expectedAuth0Domain
1389
- })) {
1390
- logger6.warn("Ignoring stored auth issued for a different runtime target", {
1391
- storedRuntimeTarget: storedAuth.runtimeTarget ?? "unrecorded",
1392
- activeRuntimeTarget,
1393
- tokenIssuer: tokenIssuer ?? "undecodable",
1394
- expectedAuth0Domain
1395
- });
1396
- return null;
1397
- }
1398
- return storedAuth;
1399
- } catch (error) {
1400
- logger6.error("Failed to load stored auth", {
1401
- error: error instanceof Error ? error.message : String(error)
1402
- });
1053
+ getResults(sessionId) {
1054
+ const resultsPath = path13.join(this.sessionsDir, sessionId, "results.md");
1055
+ if (!fs4.existsSync(resultsPath)) {
1403
1056
  return null;
1404
1057
  }
1058
+ return fs4.readFileSync(resultsPath, "utf-8");
1405
1059
  }
1406
1060
  /**
1407
- * Get the current access token (if valid).
1408
- * Does not auto-refresh - use getValidAccessToken() for that.
1061
+ * Get the data directory path.
1062
+ * @returns Data directory path.
1409
1063
  */
1410
- getAccessToken() {
1411
- const storedAuth = this.loadStoredAuth();
1412
- if (!storedAuth) {
1413
- return null;
1414
- }
1415
- const now = /* @__PURE__ */ new Date();
1416
- const expiresAt = new Date(storedAuth.expiresAt);
1417
- if (now >= expiresAt) {
1418
- return null;
1419
- }
1420
- return storedAuth.accessToken;
1064
+ getDataDir() {
1065
+ return this.dataDir;
1421
1066
  }
1422
1067
  /**
1423
- * Check if the access token is expired or about to expire.
1424
- * Uses a 5-minute buffer for safety.
1068
+ * Get the sessions directory path.
1069
+ * @returns Sessions directory path.
1425
1070
  */
1426
- isAccessTokenExpired() {
1427
- const storedAuth = this.loadStoredAuth();
1428
- if (!storedAuth) {
1429
- return true;
1430
- }
1431
- const now = /* @__PURE__ */ new Date();
1432
- const expiresAt = new Date(storedAuth.expiresAt);
1433
- const bufferMs = 5 * 60 * 1e3;
1434
- return now.getTime() >= expiresAt.getTime() - bufferMs;
1071
+ getSessionsDir() {
1072
+ return this.sessionsDir;
1435
1073
  }
1436
1074
  /**
1437
- * Refresh the access token using the stored refresh token.
1438
- * @returns New access token or null if refresh failed.
1075
+ * Create a new session with metadata.
1076
+ * @param params - Session creation parameters.
1077
+ * @returns Path to the session directory.
1439
1078
  */
1440
- async refreshAccessToken() {
1079
+ createSession(params) {
1080
+ const { sessionId, targetUrl, testInstructions } = params;
1441
1081
  const logger6 = getLogger();
1442
- const storedAuth = this.loadStoredAuth();
1443
- if (!storedAuth?.refreshToken) {
1444
- logger6.debug("No refresh token available");
1445
- return null;
1446
- }
1447
- const config = getConfig();
1448
- const { domain, clientId } = config.localQa.auth0;
1449
- logger6.info("Refreshing access token");
1450
- const url = `https://${domain}/oauth/token`;
1451
- const body = new URLSearchParams({
1452
- grant_type: "refresh_token",
1453
- client_id: clientId,
1454
- refresh_token: storedAuth.refreshToken
1455
- });
1456
- try {
1457
- const response = await fetch(url, {
1458
- method: "POST",
1459
- headers: {
1460
- "Content-Type": "application/x-www-form-urlencoded"
1461
- },
1462
- body: body.toString()
1463
- });
1464
- if (!response.ok) {
1465
- const errorText = await response.text();
1466
- logger6.error("Token refresh failed", {
1467
- status: response.status,
1468
- error: errorText
1469
- });
1470
- return null;
1471
- }
1472
- const tokenData = await response.json();
1473
- const newExpiresAt = new Date(Date.now() + tokenData.expires_in * 1e3).toISOString();
1474
- const updatedAuth = {
1475
- accessToken: tokenData.access_token,
1476
- refreshToken: tokenData.refresh_token ?? storedAuth.refreshToken,
1477
- expiresAt: newExpiresAt,
1478
- email: storedAuth.email,
1479
- userId: storedAuth.userId,
1480
- runtimeTarget: storedAuth.runtimeTarget ?? getActiveRuntimeTarget()
1481
- };
1482
- const dir = path11.dirname(this.oauthSessionFilePath);
1483
- if (!fs6.existsSync(dir)) {
1484
- fs6.mkdirSync(dir, { recursive: true });
1485
- }
1486
- fs6.writeFileSync(this.oauthSessionFilePath, JSON.stringify(updatedAuth, null, 2), {
1487
- encoding: "utf-8",
1488
- mode: 384
1489
- });
1490
- logger6.info("Access token refreshed", { expiresAt: newExpiresAt });
1491
- return tokenData.access_token;
1492
- } catch (error) {
1493
- logger6.error("Token refresh request failed", {
1494
- error: error instanceof Error ? error.message : String(error)
1495
- });
1496
- return null;
1497
- }
1082
+ const sessionDir = this.createSessionDirectory(sessionId);
1083
+ const metadata = {
1084
+ sessionId,
1085
+ workflowRunId: sessionId,
1086
+ status: "running" /* Running */,
1087
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
1088
+ targetUrl,
1089
+ testInstructions
1090
+ };
1091
+ this.saveSessionMetadata(metadata);
1092
+ this.setCurrentSession(sessionId);
1093
+ logger6.info("Created session", { sessionId, targetUrl });
1094
+ return sessionDir;
1498
1095
  }
1499
1096
  /**
1500
- * Get a valid access token, refreshing if necessary.
1501
- * This is the preferred method for getting an access token for API calls.
1502
- * @returns Valid access token or null if not authenticated or refresh failed.
1097
+ * Update the status of an existing session.
1098
+ * @param params - Status update parameters.
1503
1099
  */
1504
- async getValidAccessToken() {
1100
+ updateSessionStatus(params) {
1101
+ const { sessionId, status } = params;
1505
1102
  const logger6 = getLogger();
1506
- const storedAuth = this.loadStoredAuth();
1507
- if (!storedAuth) {
1508
- logger6.debug("No stored auth, cannot get valid token");
1509
- return null;
1510
- }
1511
- const now = /* @__PURE__ */ new Date();
1512
- const expiresAt = new Date(storedAuth.expiresAt);
1513
- const isStrictlyExpired = now >= expiresAt;
1514
- if (!isStrictlyExpired && !this.isAccessTokenExpired()) {
1515
- return storedAuth.accessToken;
1516
- }
1517
- if (!isStrictlyExpired) {
1518
- logger6.debug("Access token in buffer zone, attempting proactive refresh");
1519
- } else {
1520
- logger6.info("Access token expired, attempting refresh");
1521
- }
1522
- const refreshedToken = await this.refreshAccessToken();
1523
- if (refreshedToken) {
1524
- return refreshedToken;
1103
+ const metadata = this.loadSessionMetadata(sessionId);
1104
+ if (!metadata) {
1105
+ logger6.warn("Session not found for status update", { sessionId });
1106
+ return;
1525
1107
  }
1526
- if (!isStrictlyExpired) {
1527
- logger6.warn("Token refresh failed, but token not yet expired - using existing token");
1528
- return storedAuth.accessToken;
1108
+ metadata.status = status;
1109
+ if (status === "completed" /* Completed */ || status === "failed" /* Failed */) {
1110
+ metadata.endTime = (/* @__PURE__ */ new Date()).toISOString();
1529
1111
  }
1530
- logger6.warn("Token refresh failed and token is expired, user needs to re-authenticate");
1531
- return null;
1112
+ this.saveSessionMetadata(metadata);
1113
+ logger6.debug("Updated session status", { sessionId, status });
1532
1114
  }
1533
1115
  /**
1534
- * Clear stored authentication (logout).
1116
+ * Initialize the results.md file with a header.
1117
+ * @param params - Initialization parameters.
1535
1118
  */
1536
- logout() {
1119
+ initializeResults(params) {
1120
+ const { sessionId, targetUrl, testInstructions } = params;
1537
1121
  const logger6 = getLogger();
1538
- if (!fs6.existsSync(this.oauthSessionFilePath)) {
1539
- logger6.debug("No auth to clear");
1540
- return false;
1541
- }
1542
- try {
1543
- fs6.unlinkSync(this.oauthSessionFilePath);
1544
- logger6.info("Auth cleared successfully");
1545
- return true;
1546
- } catch (error) {
1547
- logger6.error("Failed to clear auth", {
1548
- error: error instanceof Error ? error.message : String(error)
1549
- });
1550
- return false;
1551
- }
1122
+ const sessionDir = this.createSessionDirectory(sessionId);
1123
+ const resultsPath = path13.join(sessionDir, "results.md");
1124
+ const header = [
1125
+ `# Test Results: ${sessionId}`,
1126
+ "",
1127
+ `**Target URL:** ${targetUrl}`,
1128
+ `**Started:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
1129
+ `**Instructions:** ${testInstructions}`,
1130
+ "",
1131
+ "---",
1132
+ "",
1133
+ "## Test Steps",
1134
+ ""
1135
+ ].join("\n");
1136
+ fs4.writeFileSync(resultsPath, header, "utf-8");
1137
+ logger6.debug("Initialized results.md", { sessionId });
1552
1138
  }
1553
- };
1554
- var serviceInstance = null;
1555
- function getAuthService() {
1556
- serviceInstance ??= new AuthService();
1557
- return serviceInstance;
1558
- }
1559
- function resetAuthService() {
1560
- serviceInstance = null;
1561
- }
1562
- function sleep(params) {
1563
- return new Promise((resolve6) => {
1564
- setTimeout(() => {
1565
- resolve6();
1566
- }, params.durationMs);
1567
- });
1568
- }
1569
- var DEFAULT_SESSION_MAX_AGE_DAYS = 30;
1570
- var StorageService = class {
1571
- /** Base data directory. */
1572
- dataDir;
1573
- /** Sessions directory. */
1574
- sessionsDir;
1575
1139
  /**
1576
- * Create a new StorageService.
1140
+ * Append a test step to the results.md file.
1141
+ * @param params - Step parameters.
1577
1142
  */
1578
- constructor() {
1579
- const config = getConfig();
1580
- this.dataDir = config.localQa.dataDir;
1581
- this.sessionsDir = config.localQa.sessionsDir;
1143
+ appendStepToResults(params) {
1144
+ const { sessionId, step } = params;
1145
+ const logger6 = getLogger();
1146
+ const sessionDir = this.createSessionDirectory(sessionId);
1147
+ const resultsPath = path13.join(sessionDir, "results.md");
1148
+ const statusIcon = step.success ? "\u2713" : "\u2717";
1149
+ const stepContent = [
1150
+ `### Step ${step.stepNumber}: ${step.action}`,
1151
+ "",
1152
+ step.target ? `- **Target:** ${step.target}` : "",
1153
+ `- **Result:** ${step.result} ${statusIcon}`,
1154
+ step.screenshotPath ? `- **Screenshot:** [step-${String(step.stepNumber).padStart(3, "0")}.png](screenshots/step-${String(step.stepNumber).padStart(3, "0")}.png)` : "",
1155
+ ""
1156
+ ].filter(Boolean).join("\n");
1157
+ fs4.appendFileSync(resultsPath, stepContent + "\n", "utf-8");
1158
+ logger6.debug("Appended step to results", { sessionId, stepNumber: step.stepNumber });
1159
+ const metadata = this.loadSessionMetadata(sessionId);
1160
+ if (metadata) {
1161
+ metadata.stepsCount = (metadata.stepsCount ?? 0) + 1;
1162
+ this.saveSessionMetadata(metadata);
1163
+ }
1582
1164
  }
1583
1165
  /**
1584
- * Ensure the base directories exist.
1166
+ * Finalize the results.md file with a summary.
1167
+ * @param params - Finalization parameters.
1585
1168
  */
1586
- ensureDirectories() {
1169
+ finalizeResults(params) {
1170
+ const { sessionId, status, summary } = params;
1587
1171
  const logger6 = getLogger();
1588
- if (!fs6.existsSync(this.dataDir)) {
1589
- fs6.mkdirSync(this.dataDir, { recursive: true });
1590
- logger6.info("Created data directory", { path: this.dataDir });
1172
+ const sessionDir = path13.join(this.sessionsDir, sessionId);
1173
+ const resultsPath = path13.join(sessionDir, "results.md");
1174
+ if (!fs4.existsSync(resultsPath)) {
1175
+ logger6.warn("Results file not found for finalization", { sessionId });
1176
+ return;
1591
1177
  }
1592
- if (!fs6.existsSync(this.sessionsDir)) {
1593
- fs6.mkdirSync(this.sessionsDir, { recursive: true });
1594
- logger6.info("Created sessions directory", { path: this.sessionsDir });
1178
+ const metadata = this.loadSessionMetadata(sessionId);
1179
+ const endTime = /* @__PURE__ */ new Date();
1180
+ const startTime = metadata?.startTime ? new Date(metadata.startTime) : endTime;
1181
+ const durationMs = endTime.getTime() - startTime.getTime();
1182
+ const durationSeconds = (durationMs / 1e3).toFixed(2);
1183
+ const statusDisplay = status === "completed" /* Completed */ ? "\u2713 Passed" : status === "failed" /* Failed */ ? "\u2717 Failed" : "\u2014 Running";
1184
+ const footer = [
1185
+ "",
1186
+ "---",
1187
+ "",
1188
+ "## Summary",
1189
+ "",
1190
+ `**Status:** ${statusDisplay}`,
1191
+ `**Duration:** ${durationSeconds}s`,
1192
+ `**Steps:** ${metadata?.stepsCount ?? 0}`,
1193
+ `**Completed:** ${endTime.toISOString()}`,
1194
+ "",
1195
+ summary ? summary : ""
1196
+ ].join("\n");
1197
+ fs4.appendFileSync(resultsPath, footer, "utf-8");
1198
+ logger6.debug("Finalized results.md", { sessionId, status });
1199
+ if (metadata) {
1200
+ metadata.durationMs = durationMs;
1201
+ metadata.endTime = endTime.toISOString();
1202
+ metadata.status = status;
1203
+ this.saveSessionMetadata(metadata);
1595
1204
  }
1596
1205
  }
1597
1206
  /**
1598
- * Create a new session directory.
1599
- * @param sessionId - Unique session ID.
1600
- * @returns Path to the session directory.
1207
+ * List all sessions with their metadata.
1208
+ * @returns Array of session summaries.
1601
1209
  */
1602
- createSessionDirectory(sessionId) {
1603
- const logger6 = getLogger();
1604
- this.ensureDirectories();
1605
- const sessionDir = path11.join(this.sessionsDir, sessionId);
1606
- if (!fs6.existsSync(sessionDir)) {
1607
- fs6.mkdirSync(sessionDir, { recursive: true });
1608
- fs6.mkdirSync(path11.join(sessionDir, "screenshots"), { recursive: true });
1609
- fs6.mkdirSync(path11.join(sessionDir, "logs"), { recursive: true });
1610
- logger6.info("Created session directory", { sessionId, path: sessionDir });
1210
+ listSessionsWithMetadata() {
1211
+ const sessionIds = this.listSessions();
1212
+ const summaries = [];
1213
+ for (const sessionId of sessionIds) {
1214
+ if (sessionId === "current") {
1215
+ continue;
1216
+ }
1217
+ const metadata = this.loadSessionMetadata(sessionId);
1218
+ if (metadata) {
1219
+ summaries.push({
1220
+ sessionId: metadata.sessionId,
1221
+ status: metadata.status,
1222
+ startTime: metadata.startTime,
1223
+ endTime: metadata.endTime,
1224
+ targetUrl: metadata.targetUrl,
1225
+ durationMs: metadata.durationMs,
1226
+ stepsCount: metadata.stepsCount
1227
+ });
1228
+ }
1611
1229
  }
1612
- return sessionDir;
1230
+ summaries.sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime());
1231
+ return summaries;
1613
1232
  }
1614
1233
  /**
1615
- * Save session metadata.
1616
- * @param metadata - Session metadata to save.
1234
+ * Cleanup old sessions beyond the specified age.
1235
+ * @param params - Cleanup parameters.
1236
+ * @returns Number of sessions deleted.
1617
1237
  */
1618
- saveSessionMetadata(metadata) {
1238
+ cleanupOldSessions(params) {
1239
+ const maxAgeDays = params?.maxAgeDays ?? DEFAULT_SESSION_MAX_AGE_DAYS;
1619
1240
  const logger6 = getLogger();
1620
- const sessionDir = this.createSessionDirectory(metadata.sessionId);
1621
- const metadataPath = path11.join(sessionDir, "metadata.json");
1622
- fs6.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
1623
- logger6.debug("Saved session metadata", { sessionId: metadata.sessionId });
1241
+ const cutoffDate = /* @__PURE__ */ new Date();
1242
+ cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
1243
+ const sessionIds = this.listSessions();
1244
+ let deletedCount = 0;
1245
+ for (const sessionId of sessionIds) {
1246
+ if (sessionId === "current") {
1247
+ continue;
1248
+ }
1249
+ const metadata = this.loadSessionMetadata(sessionId);
1250
+ if (!metadata) {
1251
+ continue;
1252
+ }
1253
+ const sessionDate = new Date(metadata.startTime);
1254
+ if (sessionDate < cutoffDate) {
1255
+ const sessionDir = path13.join(this.sessionsDir, sessionId);
1256
+ try {
1257
+ fs4.rmSync(sessionDir, { recursive: true, force: true });
1258
+ deletedCount++;
1259
+ logger6.info("Deleted old session", {
1260
+ sessionId,
1261
+ age: Math.floor((Date.now() - sessionDate.getTime()) / (1e3 * 60 * 60 * 24))
1262
+ });
1263
+ } catch (error) {
1264
+ logger6.error("Failed to delete session", {
1265
+ sessionId,
1266
+ error: error instanceof Error ? error.message : String(error)
1267
+ });
1268
+ }
1269
+ }
1270
+ }
1271
+ if (deletedCount > 0) {
1272
+ logger6.info("Session cleanup completed", {
1273
+ deletedCount,
1274
+ maxAgeDays
1275
+ });
1276
+ }
1277
+ return deletedCount;
1624
1278
  }
1625
1279
  /**
1626
- * Load session metadata.
1627
- * @param sessionId - Session ID to load.
1628
- * @returns Session metadata, or null if not found.
1280
+ * Get a session directory path.
1281
+ * @param sessionId - Session ID.
1282
+ * @returns Session directory path.
1629
1283
  */
1630
- loadSessionMetadata(sessionId) {
1284
+ getSessionPath(sessionId) {
1285
+ return path13.join(this.sessionsDir, sessionId);
1286
+ }
1287
+ /**
1288
+ * Delete a specific session.
1289
+ * @param sessionId - Session ID to delete.
1290
+ * @returns Whether deletion succeeded.
1291
+ */
1292
+ deleteSession(sessionId) {
1631
1293
  const logger6 = getLogger();
1632
- const metadataPath = path11.join(this.sessionsDir, sessionId, "metadata.json");
1633
- if (!fs6.existsSync(metadataPath)) {
1634
- return null;
1294
+ const sessionDir = path13.join(this.sessionsDir, sessionId);
1295
+ if (!fs4.existsSync(sessionDir)) {
1296
+ logger6.warn("Session not found for deletion", { sessionId });
1297
+ return false;
1635
1298
  }
1636
1299
  try {
1637
- const content = fs6.readFileSync(metadataPath, "utf-8");
1638
- return JSON.parse(content);
1300
+ const currentId = this.getCurrentSessionId();
1301
+ if (currentId === sessionId) {
1302
+ const currentPath = path13.join(this.sessionsDir, "current");
1303
+ if (fs4.existsSync(currentPath)) {
1304
+ fs4.unlinkSync(currentPath);
1305
+ }
1306
+ }
1307
+ fs4.rmSync(sessionDir, { recursive: true, force: true });
1308
+ logger6.info("Deleted session", { sessionId });
1309
+ return true;
1639
1310
  } catch (error) {
1640
- logger6.error("Failed to load session metadata", {
1311
+ logger6.error("Failed to delete session", {
1641
1312
  sessionId,
1642
1313
  error: error instanceof Error ? error.message : String(error)
1643
1314
  });
1644
- return null;
1315
+ return false;
1316
+ }
1317
+ }
1318
+ };
1319
+ var serviceInstance = null;
1320
+ function getStorageService() {
1321
+ serviceInstance ??= new StorageService();
1322
+ return serviceInstance;
1323
+ }
1324
+ function resetStorageService() {
1325
+ serviceInstance = null;
1326
+ }
1327
+
1328
+ // packages/mcps/src/mcp/local/services/run-result-storage-service.ts
1329
+ var logger3 = getLogger();
1330
+ var RunResultStorageService = class {
1331
+ /** Base directory for run results. */
1332
+ runResultsDir;
1333
+ /** Base directory for test scripts. */
1334
+ testScriptsDir;
1335
+ constructor() {
1336
+ const storageService = getStorageService();
1337
+ const dataDir = storageService.getDataDir();
1338
+ this.runResultsDir = path13.join(dataDir, "run-results");
1339
+ this.testScriptsDir = path13.join(dataDir, "test-scripts");
1340
+ this.ensureDirectories();
1341
+ }
1342
+ /**
1343
+ * Ensure storage directories exist.
1344
+ */
1345
+ ensureDirectories() {
1346
+ if (!fs4.existsSync(this.runResultsDir)) {
1347
+ fs4.mkdirSync(this.runResultsDir, { recursive: true });
1348
+ }
1349
+ if (!fs4.existsSync(this.testScriptsDir)) {
1350
+ fs4.mkdirSync(this.testScriptsDir, { recursive: true });
1645
1351
  }
1646
1352
  }
1353
+ // ========================================
1354
+ // Run Results
1355
+ // ========================================
1647
1356
  /**
1648
- * List all sessions.
1649
- * @returns Array of session IDs.
1357
+ * List all run results.
1650
1358
  */
1651
- listSessions() {
1652
- if (!fs6.existsSync(this.sessionsDir)) {
1359
+ listRunResults() {
1360
+ try {
1361
+ const files = fs4.readdirSync(this.runResultsDir).filter((f) => f.endsWith(".json"));
1362
+ const results = [];
1363
+ for (const file of files) {
1364
+ try {
1365
+ const content = fs4.readFileSync(path13.join(this.runResultsDir, file), "utf-8");
1366
+ results.push(JSON.parse(content));
1367
+ } catch {
1368
+ logger3.warn("Failed to read run result file", { file });
1369
+ }
1370
+ }
1371
+ return results.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
1372
+ } catch {
1653
1373
  return [];
1654
1374
  }
1655
- return fs6.readdirSync(this.sessionsDir).filter((entry) => {
1656
- const entryPath = path11.join(this.sessionsDir, entry);
1657
- return fs6.statSync(entryPath).isDirectory();
1658
- });
1659
1375
  }
1660
1376
  /**
1661
- * Get the current session ID (if any).
1662
- * @returns Current session ID, or null.
1377
+ * Get a run result by ID.
1663
1378
  */
1664
- getCurrentSessionId() {
1665
- const currentPath = path11.join(this.sessionsDir, "current");
1666
- if (!fs6.existsSync(currentPath)) {
1667
- return null;
1379
+ getRunResult(runId) {
1380
+ const filePath = path13.join(this.runResultsDir, `${runId}.json`);
1381
+ if (!fs4.existsSync(filePath)) {
1382
+ return void 0;
1668
1383
  }
1669
1384
  try {
1670
- const target = fs6.readlinkSync(currentPath);
1671
- return path11.basename(target);
1385
+ const content = fs4.readFileSync(filePath, "utf-8");
1386
+ return JSON.parse(content);
1672
1387
  } catch {
1673
- return null;
1388
+ return void 0;
1674
1389
  }
1675
1390
  }
1676
1391
  /**
1677
- * Set the current session.
1678
- * @param sessionId - Session ID to set as current.
1392
+ * Save a run result.
1679
1393
  */
1680
- setCurrentSession(sessionId) {
1681
- const logger6 = getLogger();
1682
- const currentPath = path11.join(this.sessionsDir, "current");
1683
- const targetPath = path11.join(this.sessionsDir, sessionId);
1684
- if (fs6.existsSync(currentPath)) {
1685
- fs6.unlinkSync(currentPath);
1686
- }
1687
- fs6.symlinkSync(targetPath, currentPath);
1688
- logger6.info("Set current session", { sessionId });
1394
+ saveRunResult(result) {
1395
+ const filePath = path13.join(this.runResultsDir, `${result.id}.json`);
1396
+ fs4.writeFileSync(filePath, JSON.stringify(result, null, 2));
1689
1397
  }
1690
1398
  /**
1691
- * Save a screenshot to the session directory.
1692
- * @param params - Screenshot save parameters.
1399
+ * Create a new run result.
1693
1400
  */
1694
- saveScreenshot(params) {
1695
- const { sessionId, filename, data } = params;
1696
- const logger6 = getLogger();
1697
- const sessionDir = this.createSessionDirectory(sessionId);
1698
- const screenshotPath = path11.join(sessionDir, "screenshots", filename);
1699
- fs6.writeFileSync(screenshotPath, data);
1700
- logger6.debug("Saved screenshot", { sessionId, filename });
1701
- return screenshotPath;
1401
+ createRunResult(params) {
1402
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1403
+ const id = randomUUID();
1404
+ const result = {
1405
+ id,
1406
+ runType: params.runType,
1407
+ status: "pending",
1408
+ cloudTestCaseId: params.cloudTestCaseId,
1409
+ projectId: params.projectId,
1410
+ useCaseId: params.useCaseId,
1411
+ localUrl: params.localUrl,
1412
+ productionUrl: params.productionUrl,
1413
+ localExecutionContext: params.localExecutionContext,
1414
+ createdAt: now,
1415
+ updatedAt: now
1416
+ };
1417
+ this.saveRunResult(result);
1418
+ return result;
1702
1419
  }
1703
1420
  /**
1704
- * Append to the results markdown file.
1705
- * @param params - Results append parameters.
1421
+ * Update a run result.
1706
1422
  */
1707
- appendToResults(params) {
1708
- const { sessionId, content } = params;
1709
- const logger6 = getLogger();
1710
- const sessionDir = this.createSessionDirectory(sessionId);
1711
- const resultsPath = path11.join(sessionDir, "results.md");
1712
- fs6.appendFileSync(resultsPath, content + "\n", "utf-8");
1713
- logger6.debug("Appended to results", { sessionId });
1423
+ updateRunResult(runId, updates) {
1424
+ const result = this.getRunResult(runId);
1425
+ if (!result) {
1426
+ return void 0;
1427
+ }
1428
+ const updated = {
1429
+ ...result,
1430
+ ...updates,
1431
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1432
+ };
1433
+ this.saveRunResult(updated);
1434
+ return updated;
1714
1435
  }
1436
+ // ========================================
1437
+ // Test Scripts
1438
+ // ========================================
1715
1439
  /**
1716
- * Get the results markdown content.
1717
- * @param sessionId - Session ID.
1718
- * @returns Results content, or null if not found.
1440
+ * List all test scripts.
1719
1441
  */
1720
- getResults(sessionId) {
1721
- const resultsPath = path11.join(this.sessionsDir, sessionId, "results.md");
1722
- if (!fs6.existsSync(resultsPath)) {
1723
- return null;
1442
+ listTestScripts() {
1443
+ try {
1444
+ const files = fs4.readdirSync(this.testScriptsDir).filter((f) => f.endsWith(".json"));
1445
+ const scripts = [];
1446
+ for (const file of files) {
1447
+ try {
1448
+ const content = fs4.readFileSync(path13.join(this.testScriptsDir, file), "utf-8");
1449
+ scripts.push(JSON.parse(content));
1450
+ } catch {
1451
+ logger3.warn("Failed to read test script file", { file });
1452
+ }
1453
+ }
1454
+ return scripts.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
1455
+ } catch {
1456
+ return [];
1724
1457
  }
1725
- return fs6.readFileSync(resultsPath, "utf-8");
1726
1458
  }
1727
1459
  /**
1728
- * Get the data directory path.
1729
- * @returns Data directory path.
1460
+ * Get a test script by ID.
1730
1461
  */
1731
- getDataDir() {
1732
- return this.dataDir;
1462
+ getTestScript(testScriptId) {
1463
+ const filePath = path13.join(this.testScriptsDir, `${testScriptId}.json`);
1464
+ if (!fs4.existsSync(filePath)) {
1465
+ return void 0;
1466
+ }
1467
+ try {
1468
+ const content = fs4.readFileSync(filePath, "utf-8");
1469
+ return JSON.parse(content);
1470
+ } catch {
1471
+ return void 0;
1472
+ }
1733
1473
  }
1734
1474
  /**
1735
- * Get the sessions directory path.
1736
- * @returns Sessions directory path.
1475
+ * Save a test script.
1737
1476
  */
1738
- getSessionsDir() {
1739
- return this.sessionsDir;
1477
+ saveTestScript(script) {
1478
+ const filePath = path13.join(this.testScriptsDir, `${script.id}.json`);
1479
+ fs4.writeFileSync(filePath, JSON.stringify(script, null, 2));
1480
+ }
1481
+ /**
1482
+ * Create a new test script.
1483
+ */
1484
+ createTestScript(params) {
1485
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1486
+ const id = randomUUID();
1487
+ const script = {
1488
+ id,
1489
+ name: params.name,
1490
+ url: params.url,
1491
+ status: "pending",
1492
+ cloudTestCaseId: params.cloudTestCaseId,
1493
+ goal: params.goal,
1494
+ createdAt: now,
1495
+ updatedAt: now
1496
+ };
1497
+ this.saveTestScript(script);
1498
+ return script;
1499
+ }
1500
+ /**
1501
+ * Update a test script.
1502
+ */
1503
+ updateTestScript(testScriptId, updates) {
1504
+ const script = this.getTestScript(testScriptId);
1505
+ if (!script) {
1506
+ return void 0;
1507
+ }
1508
+ const updated = {
1509
+ ...script,
1510
+ ...updates,
1511
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1512
+ };
1513
+ this.saveTestScript(updated);
1514
+ return updated;
1515
+ }
1516
+ };
1517
+ var instance = null;
1518
+ function getRunResultStorageService() {
1519
+ if (!instance) {
1520
+ instance = new RunResultStorageService();
1521
+ }
1522
+ return instance;
1523
+ }
1524
+ function resetRunResultStorageService() {
1525
+ instance = null;
1526
+ }
1527
+
1528
+ // packages/mcps/src/mcp/local/services/run-view/read-run-view.ts
1529
+ function getRunSessionsDir() {
1530
+ return path13.join(getDataDir(), RUN_SESSIONS_DIR_NAME);
1531
+ }
1532
+ var RunViewError = class extends Error {
1533
+ };
1534
+ var listSessionDirNames = (sessionsDir) => {
1535
+ if (!fs4.existsSync(sessionsDir)) {
1536
+ throw new RunViewError(`No runs directory at ${sessionsDir}`);
1537
+ }
1538
+ return fs4.readdirSync(sessionsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
1539
+ };
1540
+ function resolveRunSessionPath(runId) {
1541
+ const sessionsDir = getRunSessionsDir();
1542
+ const names = listSessionDirNames(sessionsDir);
1543
+ if (names.length === 0) {
1544
+ throw new RunViewError(`No runs found under ${sessionsDir}`);
1545
+ }
1546
+ if (!runId) {
1547
+ const newest = names.map((name) => ({ name, modifiedAtMs: fs4.statSync(path13.join(sessionsDir, name)).mtimeMs })).sort((left, right) => right.modifiedAtMs - left.modifiedAtMs)[0];
1548
+ return path13.join(sessionsDir, newest.name);
1549
+ }
1550
+ const exact = names.find((name) => name === runId);
1551
+ if (exact) {
1552
+ return path13.join(sessionsDir, exact);
1553
+ }
1554
+ const prefixed = names.filter((name) => name.startsWith(runId));
1555
+ if (prefixed.length === 1) {
1556
+ return path13.join(sessionsDir, prefixed[0]);
1557
+ }
1558
+ if (prefixed.length > 1) {
1559
+ throw new RunViewError(
1560
+ `Run id "${runId}" matches ${prefixed.length} runs (${prefixed.slice(0, 3).join(", ")}\u2026). Use more characters.`
1561
+ );
1562
+ }
1563
+ throw new RunViewError(`No run matching "${runId}" under ${sessionsDir}`);
1564
+ }
1565
+ var listScreenshotFileNames = (sessionPath) => {
1566
+ const screenshotDir = path13.join(sessionPath, ...SCREENSHOT_DIR_RELATIVE_PATH.split("/"));
1567
+ if (!fs4.existsSync(screenshotDir)) {
1568
+ return [];
1569
+ }
1570
+ return fs4.readdirSync(screenshotDir).filter((name) => name.endsWith(SCREENSHOT_FILE_EXTENSION));
1571
+ };
1572
+ var resolveScreenshotPath = (sessionPath, screenshotFileNames, recordedPath) => {
1573
+ if (!recordedPath) {
1574
+ return null;
1575
+ }
1576
+ const recordedName = recordedPath.split(/[\\/]/).pop();
1577
+ if (!recordedName) {
1578
+ return null;
1579
+ }
1580
+ const match = screenshotFileNames.find((name) => name === recordedName) ?? screenshotFileNames.find((name) => name.endsWith(recordedName));
1581
+ return match ? path13.join(sessionPath, ...SCREENSHOT_DIR_RELATIVE_PATH.split("/"), match) : null;
1582
+ };
1583
+ function readRunView(runId) {
1584
+ const sessionPath = resolveRunSessionPath(runId);
1585
+ const actionScriptPath = path13.join(sessionPath, ACTION_SCRIPT_FILE_NAME);
1586
+ if (!fs4.existsSync(actionScriptPath)) {
1587
+ throw new RunViewError(
1588
+ `Run ${path13.basename(sessionPath)} has no ${ACTION_SCRIPT_FILE_NAME} \u2014 it died before writing one. Looked in ${sessionPath}`
1589
+ );
1590
+ }
1591
+ let actionScript;
1592
+ try {
1593
+ actionScript = JSON.parse(fs4.readFileSync(actionScriptPath, "utf8"));
1594
+ } catch (parseError) {
1595
+ throw new RunViewError(
1596
+ `Could not read ${actionScriptPath}: ${parseError instanceof Error ? parseError.message : String(parseError)}`
1597
+ );
1598
+ }
1599
+ const resolvedRunId = path13.basename(sessionPath);
1600
+ const recordedOutcome = getRunResultStorageService().getRunResult(resolvedRunId)?.status ?? null;
1601
+ const screenshotFileNames = listScreenshotFileNames(sessionPath);
1602
+ const steps = (actionScript.steps ?? []).map((step, index) => ({
1603
+ index,
1604
+ action: step.operation?.action ?? "unknown",
1605
+ explanation: step.briefExplanation ?? "",
1606
+ screenshotPath: resolveScreenshotPath(sessionPath, screenshotFileNames, step.operation?.screenshot),
1607
+ url: step.operation?.url ?? null
1608
+ }));
1609
+ return {
1610
+ runId: resolvedRunId,
1611
+ sessionPath,
1612
+ title: actionScript.actionScriptName ?? null,
1613
+ status: recordedOutcome,
1614
+ verdict: actionScript.summaryStep?.briefExplanation ?? null,
1615
+ steps,
1616
+ screenshotCount: screenshotFileNames.length
1617
+ };
1618
+ }
1619
+
1620
+ // packages/mcps/src/mcp/local/services/run-view/render-run-view-text.ts
1621
+ function renderRunViewText(runView, options) {
1622
+ const header = [
1623
+ `Run ${runView.runId}${runView.title ? ` \u2014 ${runView.title}` : ""}`,
1624
+ `${runView.status ?? "status unrecorded"} \xB7 ${runView.steps.length} steps \xB7 ${runView.screenshotCount} frames`,
1625
+ runView.sessionPath,
1626
+ ""
1627
+ ];
1628
+ const stepLines = runView.steps.length === 0 ? ["(no steps recorded \u2014 the run ended before writing any)"] : runView.steps.flatMap((step) => {
1629
+ const lines = [
1630
+ `${String(step.index).padStart(2, " ")} ${step.action}${step.url ? ` \u2192 ${step.url}` : ""}`
1631
+ ];
1632
+ if (step.explanation) {
1633
+ lines.push(` ${step.explanation}`);
1634
+ }
1635
+ lines.push(` ${step.screenshotPath ?? "(no frame recorded)"}`);
1636
+ return lines;
1637
+ });
1638
+ const verdict = runView.verdict ? ["", `Verdict: ${runView.verdict}`] : [];
1639
+ const offer = options.includeHtmlOffer ? ["", HTML_VIEW_OFFER_LINE] : [];
1640
+ return [...header, ...stepLines, ...verdict, ...offer].join("\n");
1641
+ }
1642
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1643
+ var inlineFrame = (screenshotPath) => {
1644
+ if (!screenshotPath || !fs4.existsSync(screenshotPath)) {
1645
+ return null;
1646
+ }
1647
+ try {
1648
+ return `data:image/jpeg;base64,${fs4.readFileSync(screenshotPath).toString("base64")}`;
1649
+ } catch {
1650
+ return null;
1651
+ }
1652
+ };
1653
+ var renderStep = (step) => {
1654
+ const frame = inlineFrame(step.screenshotPath);
1655
+ const frameMarkup = frame ? `<a href="${frame}" target="_blank" rel="noreferrer"><img src="${frame}" alt="Step ${step.index} frame" loading="lazy"></a>` : `<p class="no-frame">no frame recorded</p>`;
1656
+ return `<li class="step">
1657
+ <div class="meta">
1658
+ <span class="index">${step.index}</span>
1659
+ <span class="action">${escapeHtml(step.action)}</span>
1660
+ ${step.url ? `<span class="url">${escapeHtml(step.url)}</span>` : ""}
1661
+ </div>
1662
+ <p class="explanation">${escapeHtml(step.explanation || "(no explanation recorded)")}</p>
1663
+ ${frameMarkup}
1664
+ </li>`;
1665
+ };
1666
+ function renderRunViewHtml(runView) {
1667
+ const title = `Run ${runView.runId}${runView.title ? ` \u2014 ${runView.title}` : ""}`;
1668
+ return `<!doctype html>
1669
+ <html lang="en">
1670
+ <head>
1671
+ <meta charset="utf-8">
1672
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1673
+ <title>${escapeHtml(title)}</title>
1674
+ <style>
1675
+ :root { color-scheme: light dark; --bg:#ffffff; --fg:#1a1a1a; --muted:#666; --line:#e2e2e2; --chip:#f1f1f4; }
1676
+ @media (prefers-color-scheme: dark) {
1677
+ :root { --bg:#16161a; --fg:#ececf0; --muted:#9a9aa5; --line:#2c2c33; --chip:#24242b; }
1678
+ }
1679
+ body { margin:0; padding:24px 16px 64px; background:var(--bg); color:var(--fg);
1680
+ font:14px/1.5 system-ui,-apple-system,Segoe UI,sans-serif; }
1681
+ .wrap { max-width: 1040px; margin: 0 auto; }
1682
+ h1 { font-size:20px; margin:0 0 4px; }
1683
+ .sub { color:var(--muted); margin:0 0 4px; }
1684
+ .path { color:var(--muted); font-family:ui-monospace,Menlo,Consolas,monospace; font-size:12px;
1685
+ word-break:break-all; margin:0 0 24px; }
1686
+ .verdict { border:1px solid var(--line); border-left:3px solid #c0392b; border-radius:6px;
1687
+ padding:12px 14px; margin:0 0 24px; background:var(--chip); }
1688
+ .verdict h2 { font-size:13px; text-transform:uppercase; letter-spacing:.04em; margin:0 0 6px; color:var(--muted); }
1689
+ ol { list-style:none; margin:0; padding:0; }
1690
+ .step { border-top:1px solid var(--line); padding:20px 0; }
1691
+ .meta { display:flex; gap:10px; align-items:baseline; flex-wrap:wrap; }
1692
+ .index { font-family:ui-monospace,Menlo,Consolas,monospace; color:var(--muted); }
1693
+ .action { font-weight:600; }
1694
+ .url { color:var(--muted); font-family:ui-monospace,Menlo,Consolas,monospace; font-size:12px; word-break:break-all; }
1695
+ .explanation { margin:6px 0 12px; }
1696
+ .no-frame { color:var(--muted); font-style:italic; margin:0; }
1697
+ img { max-width:100%; height:auto; border:1px solid var(--line); border-radius:6px; display:block; }
1698
+ </style>
1699
+ </head>
1700
+ <body>
1701
+ <div class="wrap">
1702
+ <h1>${escapeHtml(title)}</h1>
1703
+ <p class="sub">${escapeHtml(runView.status ?? "status unrecorded")} \xB7 ${runView.steps.length} steps \xB7 ${runView.screenshotCount} frames</p>
1704
+ <p class="path">${escapeHtml(runView.sessionPath)}</p>
1705
+ ${runView.verdict ? `<div class="verdict"><h2>Verdict the run recorded</h2><p>${escapeHtml(runView.verdict)}</p></div>` : ""}
1706
+ <ol>
1707
+ ${runView.steps.map(renderStep).join("\n")}
1708
+ </ol>
1709
+ ${runView.steps.length === 0 ? '<p class="no-frame">No steps recorded \u2014 the run ended before writing any.</p>' : ""}
1710
+ </div>
1711
+ </body>
1712
+ </html>`;
1713
+ }
1714
+ function writeRunViewHtml(runView, outputDir) {
1715
+ const targetDir = outputDir ?? process.cwd();
1716
+ fs4.mkdirSync(targetDir, { recursive: true });
1717
+ const targetPath = path13.join(targetDir, `run-${runView.runId}.html`);
1718
+ fs4.writeFileSync(targetPath, renderRunViewHtml(runView), "utf8");
1719
+ return targetPath;
1720
+ }
1721
+ var API_KEY_FILE2 = "api-key.json";
1722
+ function getApiKeyFilePath() {
1723
+ return path13.join(getDataDir(), API_KEY_FILE2);
1724
+ }
1725
+ function ensureDataDir() {
1726
+ const dataDir = getDataDir();
1727
+ if (!fs4.existsSync(dataDir)) {
1728
+ fs4.mkdirSync(dataDir, { recursive: true });
1729
+ }
1730
+ }
1731
+ function loadApiKeyData() {
1732
+ const logger6 = getLogger();
1733
+ const apiKeyPath = getApiKeyFilePath();
1734
+ try {
1735
+ if (!fs4.existsSync(apiKeyPath)) {
1736
+ logger6.debug("No API key file found", { path: apiKeyPath });
1737
+ return null;
1738
+ }
1739
+ const content = fs4.readFileSync(apiKeyPath, "utf-8");
1740
+ const data = JSON.parse(content);
1741
+ return data;
1742
+ } catch (error) {
1743
+ logger6.warn("Failed to load API key data", {
1744
+ error: error instanceof Error ? error.message : String(error)
1745
+ });
1746
+ return null;
1747
+ }
1748
+ }
1749
+ function saveApiKeyData(data) {
1750
+ const logger6 = getLogger();
1751
+ const apiKeyPath = getApiKeyFilePath();
1752
+ try {
1753
+ ensureDataDir();
1754
+ const content = JSON.stringify(data, null, 2);
1755
+ fs4.writeFileSync(apiKeyPath, content, { mode: 384 });
1756
+ logger6.info("API key saved", { path: apiKeyPath });
1757
+ } catch (error) {
1758
+ logger6.error("Failed to save API key", {
1759
+ error: error instanceof Error ? error.message : String(error)
1760
+ });
1761
+ throw error;
1762
+ }
1763
+ }
1764
+ function deleteApiKeyData() {
1765
+ const logger6 = getLogger();
1766
+ const apiKeyPath = getApiKeyFilePath();
1767
+ try {
1768
+ if (fs4.existsSync(apiKeyPath)) {
1769
+ fs4.unlinkSync(apiKeyPath);
1770
+ logger6.info("API key deleted", { path: apiKeyPath });
1771
+ }
1772
+ } catch (error) {
1773
+ logger6.warn("Failed to delete API key", {
1774
+ error: error instanceof Error ? error.message : String(error)
1775
+ });
1776
+ }
1777
+ }
1778
+ function getValidApiKeyData() {
1779
+ const data = loadApiKeyData();
1780
+ if (!data) {
1781
+ return null;
1782
+ }
1783
+ if (data.apiKey) {
1784
+ return data;
1785
+ }
1786
+ return null;
1787
+ }
1788
+ function hasApiKey() {
1789
+ const data = loadApiKeyData();
1790
+ return !!data?.apiKey;
1791
+ }
1792
+ function getApiKey() {
1793
+ const data = loadApiKeyData();
1794
+ return data?.apiKey ?? null;
1795
+ }
1796
+ function saveApiKey(params) {
1797
+ const logger6 = getLogger();
1798
+ const apiKeyPath = getApiKeyFilePath();
1799
+ try {
1800
+ ensureDataDir();
1801
+ const data = {
1802
+ accessToken: "",
1803
+ expiresAt: "",
1804
+ apiKey: params.apiKey,
1805
+ apiKeyId: params.apiKeyId
1806
+ };
1807
+ const content = JSON.stringify(data, null, 2);
1808
+ fs4.writeFileSync(apiKeyPath, content, { mode: 384 });
1809
+ logger6.info("API key saved", { path: apiKeyPath });
1810
+ } catch (error) {
1811
+ logger6.error("Failed to save API key", {
1812
+ error: error instanceof Error ? error.message : String(error)
1813
+ });
1814
+ throw error;
1815
+ }
1816
+ }
1817
+ var loadCredentials = loadApiKeyData;
1818
+ var saveCredentials = saveApiKeyData;
1819
+ var deleteCredentials = deleteApiKeyData;
1820
+ var getValidCredentials = getValidApiKeyData;
1821
+ var getCredentialsFilePath = getApiKeyFilePath;
1822
+
1823
+ // packages/mcps/src/mcp/local/services/stored-auth-target.ts
1824
+ function decodeJwtPayload(token) {
1825
+ try {
1826
+ const parts = token.split(".");
1827
+ if (parts.length !== 3) {
1828
+ return null;
1829
+ }
1830
+ const payload = parts[1];
1831
+ const decoded = Buffer.from(payload, "base64url").toString("utf-8");
1832
+ return JSON.parse(decoded);
1833
+ } catch {
1834
+ return null;
1835
+ }
1836
+ }
1837
+ function extractJwtIssuer(token) {
1838
+ const payload = decodeJwtPayload(token);
1839
+ if (!payload || typeof payload.iss !== "string") {
1840
+ return null;
1841
+ }
1842
+ return payload.iss;
1843
+ }
1844
+ function normalizeIssuerHost(issuer) {
1845
+ return issuer.replace(/^https:\/\//, "").replace(/\/$/, "");
1846
+ }
1847
+ function isStoredAuthForRuntimeTarget(params) {
1848
+ if (params.tokenIssuer && params.expectedAuth0Domain) {
1849
+ const issuerHost = normalizeIssuerHost(params.tokenIssuer);
1850
+ const expectedHost = normalizeIssuerHost(params.expectedAuth0Domain);
1851
+ if (issuerHost !== expectedHost) {
1852
+ return false;
1853
+ }
1854
+ }
1855
+ if (!params.storedRuntimeTarget) {
1856
+ return params.activeRuntimeTarget === "production" /* Production */;
1857
+ }
1858
+ return params.storedRuntimeTarget === params.activeRuntimeTarget;
1859
+ }
1860
+
1861
+ // packages/mcps/src/mcp/local/services/auth-service.ts
1862
+ var DEFAULT_LOGIN_WAIT_TIMEOUT_MS = 12e4;
1863
+ var AuthService = class {
1864
+ /** Path to the OAuth session file. */
1865
+ oauthSessionFilePath;
1866
+ /** Path to the pending device code file. */
1867
+ pendingDeviceCodePath;
1868
+ /**
1869
+ * Create a new AuthService.
1870
+ */
1871
+ constructor() {
1872
+ const config = getConfig();
1873
+ this.oauthSessionFilePath = config.localQa.oauthSessionFilePath;
1874
+ this.pendingDeviceCodePath = path13.join(
1875
+ path13.dirname(config.localQa.oauthSessionFilePath),
1876
+ "pending-device-code.json"
1877
+ );
1740
1878
  }
1741
1879
  /**
1742
- * Create a new session with metadata.
1743
- * @param params - Session creation parameters.
1744
- * @returns Path to the session directory.
1880
+ * Get current authentication status.
1745
1881
  */
1746
- createSession(params) {
1747
- const { sessionId, targetUrl, testInstructions } = params;
1882
+ getAuthStatus() {
1748
1883
  const logger6 = getLogger();
1749
- const sessionDir = this.createSessionDirectory(sessionId);
1750
- const metadata = {
1751
- sessionId,
1752
- workflowRunId: sessionId,
1753
- status: "running" /* Running */,
1754
- startTime: (/* @__PURE__ */ new Date()).toISOString(),
1755
- targetUrl,
1756
- testInstructions
1884
+ const storedAuth = this.loadStoredAuth();
1885
+ if (!storedAuth) {
1886
+ logger6.debug("No stored auth found");
1887
+ return { authenticated: false };
1888
+ }
1889
+ const now = /* @__PURE__ */ new Date();
1890
+ const expiresAt = new Date(storedAuth.expiresAt);
1891
+ const isExpired = now >= expiresAt;
1892
+ logger6.debug("Auth status checked", {
1893
+ email: storedAuth.email,
1894
+ isExpired,
1895
+ expiresAt: storedAuth.expiresAt
1896
+ });
1897
+ return {
1898
+ authenticated: !isExpired,
1899
+ email: storedAuth.email,
1900
+ userId: storedAuth.userId,
1901
+ expiresAt: storedAuth.expiresAt,
1902
+ isExpired
1757
1903
  };
1758
- this.saveSessionMetadata(metadata);
1759
- this.setCurrentSession(sessionId);
1760
- logger6.info("Created session", { sessionId, targetUrl });
1761
- return sessionDir;
1762
1904
  }
1763
1905
  /**
1764
- * Update the status of an existing session.
1765
- * @param params - Status update parameters.
1906
+ * Start the device code flow.
1907
+ * @param options.forceNewSession - Clear existing Auth0 browser session before login to allow account switching.
1766
1908
  */
1767
- updateSessionStatus(params) {
1768
- const { sessionId, status } = params;
1909
+ async startDeviceCodeFlow(options) {
1769
1910
  const logger6 = getLogger();
1770
- const metadata = this.loadSessionMetadata(sessionId);
1771
- if (!metadata) {
1772
- logger6.warn("Session not found for status update", { sessionId });
1773
- return;
1911
+ const config = getConfig();
1912
+ const { domain, clientId, audience, scopes } = config.localQa.auth0;
1913
+ logger6.info("Starting device code flow");
1914
+ const url = `https://${domain}/oauth/device/code`;
1915
+ const body = new URLSearchParams({
1916
+ client_id: clientId,
1917
+ scope: scopes.join(" "),
1918
+ audience
1919
+ });
1920
+ const response = await fetch(url, {
1921
+ method: "POST",
1922
+ headers: {
1923
+ "Content-Type": "application/x-www-form-urlencoded"
1924
+ },
1925
+ body: body.toString()
1926
+ });
1927
+ if (!response.ok) {
1928
+ const errorText = await response.text();
1929
+ logger6.error("Device code request failed", {
1930
+ status: response.status,
1931
+ error: errorText
1932
+ });
1933
+ throw new Error(`Failed to start device code flow: ${response.status} ${errorText}`);
1774
1934
  }
1775
- metadata.status = status;
1776
- if (status === "completed" /* Completed */ || status === "failed" /* Failed */) {
1777
- metadata.endTime = (/* @__PURE__ */ new Date()).toISOString();
1935
+ const deviceCodeResponse = await response.json();
1936
+ logger6.info("Device code flow started", {
1937
+ userCode: deviceCodeResponse.user_code,
1938
+ verificationUri: deviceCodeResponse.verification_uri,
1939
+ expiresIn: deviceCodeResponse.expires_in
1940
+ });
1941
+ this.storePendingDeviceCode({
1942
+ deviceCode: deviceCodeResponse.device_code,
1943
+ userCode: deviceCodeResponse.user_code,
1944
+ expiresAt: new Date(Date.now() + deviceCodeResponse.expires_in * 1e3).toISOString()
1945
+ });
1946
+ let browserUrl = deviceCodeResponse.verification_uri_complete;
1947
+ if (options?.forceNewSession) {
1948
+ this.logout();
1949
+ const logoutUrl = new URL(`https://${domain}/v2/logout`);
1950
+ logoutUrl.searchParams.set("client_id", clientId);
1951
+ logoutUrl.searchParams.set("returnTo", deviceCodeResponse.verification_uri_complete);
1952
+ browserUrl = logoutUrl.toString();
1953
+ logger6.info("Force new session: cleared local token, opening logout-redirect URL", {
1954
+ logoutUrl: browserUrl
1955
+ });
1778
1956
  }
1779
- this.saveSessionMetadata(metadata);
1780
- logger6.debug("Updated session status", { sessionId, status });
1957
+ const browserOpenResult = await openBrowserUrl({
1958
+ url: browserUrl
1959
+ });
1960
+ if (browserOpenResult.opened) {
1961
+ logger6.info("Browser opened for device code login");
1962
+ } else {
1963
+ logger6.warn("Failed to open browser for device code login", {
1964
+ error: browserOpenResult.error,
1965
+ url: browserUrl
1966
+ });
1967
+ }
1968
+ return {
1969
+ deviceCode: deviceCodeResponse.device_code,
1970
+ userCode: deviceCodeResponse.user_code,
1971
+ verificationUri: deviceCodeResponse.verification_uri,
1972
+ verificationUriComplete: deviceCodeResponse.verification_uri_complete,
1973
+ expiresIn: deviceCodeResponse.expires_in,
1974
+ interval: deviceCodeResponse.interval,
1975
+ browserOpened: browserOpenResult.opened,
1976
+ browserOpenError: browserOpenResult.error
1977
+ };
1781
1978
  }
1782
1979
  /**
1783
- * Initialize the results.md file with a header.
1784
- * @param params - Initialization parameters.
1980
+ * Store a pending device code for later retrieval.
1785
1981
  */
1786
- initializeResults(params) {
1787
- const { sessionId, targetUrl, testInstructions } = params;
1982
+ storePendingDeviceCode(params) {
1788
1983
  const logger6 = getLogger();
1789
- const sessionDir = this.createSessionDirectory(sessionId);
1790
- const resultsPath = path11.join(sessionDir, "results.md");
1791
- const header = [
1792
- `# Test Results: ${sessionId}`,
1793
- "",
1794
- `**Target URL:** ${targetUrl}`,
1795
- `**Started:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
1796
- `**Instructions:** ${testInstructions}`,
1797
- "",
1798
- "---",
1799
- "",
1800
- "## Test Steps",
1801
- ""
1802
- ].join("\n");
1803
- fs6.writeFileSync(resultsPath, header, "utf-8");
1804
- logger6.debug("Initialized results.md", { sessionId });
1984
+ const dir = path13.dirname(this.pendingDeviceCodePath);
1985
+ if (!fs4.existsSync(dir)) {
1986
+ fs4.mkdirSync(dir, { recursive: true });
1987
+ }
1988
+ fs4.writeFileSync(this.pendingDeviceCodePath, JSON.stringify(params, null, 2), {
1989
+ encoding: "utf-8",
1990
+ mode: 384
1991
+ });
1992
+ logger6.debug("Pending device code stored", { userCode: params.userCode });
1805
1993
  }
1806
1994
  /**
1807
- * Append a test step to the results.md file.
1808
- * @param params - Step parameters.
1995
+ * Get the pending device code if one exists and is not expired.
1809
1996
  */
1810
- appendStepToResults(params) {
1811
- const { sessionId, step } = params;
1997
+ getPendingDeviceCode() {
1812
1998
  const logger6 = getLogger();
1813
- const sessionDir = this.createSessionDirectory(sessionId);
1814
- const resultsPath = path11.join(sessionDir, "results.md");
1815
- const statusIcon = step.success ? "\u2713" : "\u2717";
1816
- const stepContent = [
1817
- `### Step ${step.stepNumber}: ${step.action}`,
1818
- "",
1819
- step.target ? `- **Target:** ${step.target}` : "",
1820
- `- **Result:** ${step.result} ${statusIcon}`,
1821
- step.screenshotPath ? `- **Screenshot:** [step-${String(step.stepNumber).padStart(3, "0")}.png](screenshots/step-${String(step.stepNumber).padStart(3, "0")}.png)` : "",
1822
- ""
1823
- ].filter(Boolean).join("\n");
1824
- fs6.appendFileSync(resultsPath, stepContent + "\n", "utf-8");
1825
- logger6.debug("Appended step to results", { sessionId, stepNumber: step.stepNumber });
1826
- const metadata = this.loadSessionMetadata(sessionId);
1827
- if (metadata) {
1828
- metadata.stepsCount = (metadata.stepsCount ?? 0) + 1;
1829
- this.saveSessionMetadata(metadata);
1999
+ if (!fs4.existsSync(this.pendingDeviceCodePath)) {
2000
+ logger6.debug("No pending device code found");
2001
+ return null;
2002
+ }
2003
+ try {
2004
+ const content = fs4.readFileSync(this.pendingDeviceCodePath, "utf-8");
2005
+ const pendingDeviceCodeData = JSON.parse(content);
2006
+ const now = /* @__PURE__ */ new Date();
2007
+ const expiresAt = new Date(pendingDeviceCodeData.expiresAt);
2008
+ if (now >= expiresAt) {
2009
+ logger6.debug("Pending device code expired");
2010
+ this.clearPendingDeviceCode();
2011
+ return null;
2012
+ }
2013
+ return pendingDeviceCodeData.deviceCode;
2014
+ } catch (error) {
2015
+ logger6.warn("Failed to read pending device code", {
2016
+ error: error instanceof Error ? error.message : String(error)
2017
+ });
2018
+ return null;
1830
2019
  }
1831
2020
  }
1832
2021
  /**
1833
- * Finalize the results.md file with a summary.
1834
- * @param params - Finalization parameters.
2022
+ * Clear the pending device code file.
1835
2023
  */
1836
- finalizeResults(params) {
1837
- const { sessionId, status, summary } = params;
2024
+ clearPendingDeviceCode() {
1838
2025
  const logger6 = getLogger();
1839
- const sessionDir = path11.join(this.sessionsDir, sessionId);
1840
- const resultsPath = path11.join(sessionDir, "results.md");
1841
- if (!fs6.existsSync(resultsPath)) {
1842
- logger6.warn("Results file not found for finalization", { sessionId });
1843
- return;
1844
- }
1845
- const metadata = this.loadSessionMetadata(sessionId);
1846
- const endTime = /* @__PURE__ */ new Date();
1847
- const startTime = metadata?.startTime ? new Date(metadata.startTime) : endTime;
1848
- const durationMs = endTime.getTime() - startTime.getTime();
1849
- const durationSeconds = (durationMs / 1e3).toFixed(2);
1850
- const statusDisplay = status === "completed" /* Completed */ ? "\u2713 Passed" : status === "failed" /* Failed */ ? "\u2717 Failed" : "\u2014 Running";
1851
- const footer = [
1852
- "",
1853
- "---",
1854
- "",
1855
- "## Summary",
1856
- "",
1857
- `**Status:** ${statusDisplay}`,
1858
- `**Duration:** ${durationSeconds}s`,
1859
- `**Steps:** ${metadata?.stepsCount ?? 0}`,
1860
- `**Completed:** ${endTime.toISOString()}`,
1861
- "",
1862
- summary ? summary : ""
1863
- ].join("\n");
1864
- fs6.appendFileSync(resultsPath, footer, "utf-8");
1865
- logger6.debug("Finalized results.md", { sessionId, status });
1866
- if (metadata) {
1867
- metadata.durationMs = durationMs;
1868
- metadata.endTime = endTime.toISOString();
1869
- metadata.status = status;
1870
- this.saveSessionMetadata(metadata);
2026
+ if (fs4.existsSync(this.pendingDeviceCodePath)) {
2027
+ try {
2028
+ fs4.unlinkSync(this.pendingDeviceCodePath);
2029
+ logger6.debug("Pending device code cleared");
2030
+ } catch (error) {
2031
+ logger6.warn("Failed to clear pending device code", {
2032
+ error: error instanceof Error ? error.message : String(error)
2033
+ });
2034
+ }
1871
2035
  }
1872
2036
  }
1873
2037
  /**
1874
- * List all sessions with their metadata.
1875
- * @returns Array of session summaries.
2038
+ * Poll for device code authorization completion.
1876
2039
  */
1877
- listSessionsWithMetadata() {
1878
- const sessionIds = this.listSessions();
1879
- const summaries = [];
1880
- for (const sessionId of sessionIds) {
1881
- if (sessionId === "current") {
1882
- continue;
1883
- }
1884
- const metadata = this.loadSessionMetadata(sessionId);
1885
- if (metadata) {
1886
- summaries.push({
1887
- sessionId: metadata.sessionId,
1888
- status: metadata.status,
1889
- startTime: metadata.startTime,
1890
- endTime: metadata.endTime,
1891
- targetUrl: metadata.targetUrl,
1892
- durationMs: metadata.durationMs,
1893
- stepsCount: metadata.stepsCount
2040
+ async pollDeviceCode(deviceCode) {
2041
+ const logger6 = getLogger();
2042
+ const config = getConfig();
2043
+ const { domain, clientId } = config.localQa.auth0;
2044
+ logger6.debug("Polling for device code authorization");
2045
+ const url = `https://${domain}/oauth/token`;
2046
+ const body = new URLSearchParams({
2047
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
2048
+ client_id: clientId,
2049
+ device_code: deviceCode
2050
+ });
2051
+ try {
2052
+ const response = await fetch(url, {
2053
+ method: "POST",
2054
+ headers: {
2055
+ "Content-Type": "application/x-www-form-urlencoded"
2056
+ },
2057
+ body: body.toString()
2058
+ });
2059
+ if (response.ok) {
2060
+ const tokenData = await response.json();
2061
+ const tokenResponse = {
2062
+ accessToken: tokenData.access_token,
2063
+ refreshToken: tokenData.refresh_token,
2064
+ tokenType: tokenData.token_type,
2065
+ expiresIn: tokenData.expires_in
2066
+ };
2067
+ const userInfo = await this.getUserInfo(tokenResponse.accessToken);
2068
+ await this.storeAuth({
2069
+ tokenResponse,
2070
+ email: userInfo.email,
2071
+ userId: userInfo.sub
1894
2072
  });
2073
+ this.clearPendingDeviceCode();
2074
+ logger6.info("Device code authorization complete", { email: userInfo.email });
2075
+ return {
2076
+ status: "complete" /* Complete */,
2077
+ message: "Authentication successful!",
2078
+ email: userInfo.email
2079
+ };
2080
+ }
2081
+ const errorData = await response.json();
2082
+ if (errorData.error === "authorization_pending") {
2083
+ logger6.debug("Authorization pending");
2084
+ return {
2085
+ status: "pending" /* Pending */,
2086
+ message: "Waiting for user to complete authorization..."
2087
+ };
2088
+ }
2089
+ if (errorData.error === "slow_down") {
2090
+ logger6.debug("Polling too fast");
2091
+ return {
2092
+ status: "pending" /* Pending */,
2093
+ message: "Polling too fast, slowing down..."
2094
+ };
1895
2095
  }
2096
+ if (errorData.error === "expired_token") {
2097
+ logger6.warn("Device code expired");
2098
+ return {
2099
+ status: "expired" /* Expired */,
2100
+ message: "The authorization code has expired. Please start again."
2101
+ };
2102
+ }
2103
+ if (errorData.error === "access_denied") {
2104
+ logger6.warn("Access denied");
2105
+ return {
2106
+ status: "error" /* Error */,
2107
+ message: "Access was denied by the user.",
2108
+ error: errorData.error_description ?? errorData.error
2109
+ };
2110
+ }
2111
+ logger6.error("Unexpected error during polling", { error: errorData });
2112
+ return {
2113
+ status: "error" /* Error */,
2114
+ message: errorData.error_description ?? errorData.error,
2115
+ error: errorData.error
2116
+ };
2117
+ } catch (error) {
2118
+ const errorMessage = error instanceof Error ? error.message : String(error);
2119
+ logger6.error("Poll request failed", { error: errorMessage });
2120
+ return {
2121
+ status: "error" /* Error */,
2122
+ message: `Poll request failed: ${errorMessage}`,
2123
+ error: errorMessage
2124
+ };
1896
2125
  }
1897
- summaries.sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime());
1898
- return summaries;
1899
2126
  }
1900
2127
  /**
1901
- * Cleanup old sessions beyond the specified age.
1902
- * @param params - Cleanup parameters.
1903
- * @returns Number of sessions deleted.
2128
+ * Poll for device code authorization until completion or timeout.
1904
2129
  */
1905
- cleanupOldSessions(params) {
1906
- const maxAgeDays = params?.maxAgeDays ?? DEFAULT_SESSION_MAX_AGE_DAYS;
2130
+ async waitForDeviceCodeAuthorization(params) {
1907
2131
  const logger6 = getLogger();
1908
- const cutoffDate = /* @__PURE__ */ new Date();
1909
- cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
1910
- const sessionIds = this.listSessions();
1911
- let deletedCount = 0;
1912
- for (const sessionId of sessionIds) {
1913
- if (sessionId === "current") {
1914
- continue;
1915
- }
1916
- const metadata = this.loadSessionMetadata(sessionId);
1917
- if (!metadata) {
1918
- continue;
2132
+ const timeoutMs = params.timeoutMs ?? DEFAULT_LOGIN_WAIT_TIMEOUT_MS;
2133
+ const pollIntervalMs = Math.max(params.intervalSeconds, 1) * 1e3;
2134
+ const startedAt = Date.now();
2135
+ logger6.info("Waiting for device code authorization", {
2136
+ timeoutMs,
2137
+ pollIntervalMs
2138
+ });
2139
+ while (Date.now() - startedAt < timeoutMs) {
2140
+ const pollResult = await this.pollDeviceCode(params.deviceCode);
2141
+ if (pollResult.status !== "pending" /* Pending */) {
2142
+ return pollResult;
1919
2143
  }
1920
- const sessionDate = new Date(metadata.startTime);
1921
- if (sessionDate < cutoffDate) {
1922
- const sessionDir = path11.join(this.sessionsDir, sessionId);
1923
- try {
1924
- fs6.rmSync(sessionDir, { recursive: true, force: true });
1925
- deletedCount++;
1926
- logger6.info("Deleted old session", {
1927
- sessionId,
1928
- age: Math.floor((Date.now() - sessionDate.getTime()) / (1e3 * 60 * 60 * 24))
1929
- });
1930
- } catch (error) {
1931
- logger6.error("Failed to delete session", {
1932
- sessionId,
1933
- error: error instanceof Error ? error.message : String(error)
1934
- });
1935
- }
2144
+ const remainingMs = timeoutMs - (Date.now() - startedAt);
2145
+ if (remainingMs <= 0) {
2146
+ break;
1936
2147
  }
2148
+ await sleep({ durationMs: Math.min(pollIntervalMs, remainingMs) });
1937
2149
  }
1938
- if (deletedCount > 0) {
1939
- logger6.info("Session cleanup completed", {
1940
- deletedCount,
1941
- maxAgeDays
1942
- });
1943
- }
1944
- return deletedCount;
1945
- }
1946
- /**
1947
- * Get a session directory path.
1948
- * @param sessionId - Session ID.
1949
- * @returns Session directory path.
1950
- */
1951
- getSessionPath(sessionId) {
1952
- return path11.join(this.sessionsDir, sessionId);
2150
+ return {
2151
+ status: "pending" /* Pending */,
2152
+ message: "Timed out waiting for browser login confirmation. Please finish login in your browser, then call `muggle_auth_poll` to continue."
2153
+ };
1953
2154
  }
1954
2155
  /**
1955
- * Delete a specific session.
1956
- * @param sessionId - Session ID to delete.
1957
- * @returns Whether deletion succeeded.
2156
+ * Get user info from Auth0.
1958
2157
  */
1959
- deleteSession(sessionId) {
2158
+ async getUserInfo(accessToken) {
1960
2159
  const logger6 = getLogger();
1961
- const sessionDir = path11.join(this.sessionsDir, sessionId);
1962
- if (!fs6.existsSync(sessionDir)) {
1963
- logger6.warn("Session not found for deletion", { sessionId });
1964
- return false;
1965
- }
2160
+ const config = getConfig();
2161
+ const { domain } = config.localQa.auth0;
2162
+ const url = `https://${domain}/userinfo`;
1966
2163
  try {
1967
- const currentId = this.getCurrentSessionId();
1968
- if (currentId === sessionId) {
1969
- const currentPath = path11.join(this.sessionsDir, "current");
1970
- if (fs6.existsSync(currentPath)) {
1971
- fs6.unlinkSync(currentPath);
2164
+ const response = await fetch(url, {
2165
+ method: "GET",
2166
+ headers: {
2167
+ Authorization: `Bearer ${accessToken}`
1972
2168
  }
2169
+ });
2170
+ if (!response.ok) {
2171
+ logger6.warn("Failed to get user info", { status: response.status });
2172
+ return {};
1973
2173
  }
1974
- fs6.rmSync(sessionDir, { recursive: true, force: true });
1975
- logger6.info("Deleted session", { sessionId });
1976
- return true;
2174
+ const userInfoData = await response.json();
2175
+ return userInfoData;
1977
2176
  } catch (error) {
1978
- logger6.error("Failed to delete session", {
1979
- sessionId,
2177
+ logger6.warn("User info request failed", {
1980
2178
  error: error instanceof Error ? error.message : String(error)
1981
2179
  });
1982
- return false;
2180
+ return {};
1983
2181
  }
1984
2182
  }
1985
- };
1986
- var serviceInstance2 = null;
1987
- function getStorageService() {
1988
- serviceInstance2 ??= new StorageService();
1989
- return serviceInstance2;
1990
- }
1991
- function resetStorageService() {
1992
- serviceInstance2 = null;
1993
- }
1994
- var logger3 = getLogger();
1995
- var RunResultStorageService = class {
1996
- /** Base directory for run results. */
1997
- runResultsDir;
1998
- /** Base directory for test scripts. */
1999
- testScriptsDir;
2000
- constructor() {
2001
- const storageService = getStorageService();
2002
- const dataDir = storageService.getDataDir();
2003
- this.runResultsDir = path11.join(dataDir, "run-results");
2004
- this.testScriptsDir = path11.join(dataDir, "test-scripts");
2005
- this.ensureDirectories();
2006
- }
2007
2183
  /**
2008
- * Ensure storage directories exist.
2184
+ * Store authentication tokens.
2009
2185
  */
2010
- ensureDirectories() {
2011
- if (!fs6.existsSync(this.runResultsDir)) {
2012
- fs6.mkdirSync(this.runResultsDir, { recursive: true });
2013
- }
2014
- if (!fs6.existsSync(this.testScriptsDir)) {
2015
- fs6.mkdirSync(this.testScriptsDir, { recursive: true });
2186
+ async storeAuth(params) {
2187
+ const { tokenResponse, email, userId } = params;
2188
+ const logger6 = getLogger();
2189
+ const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1e3).toISOString();
2190
+ const storedAuth = {
2191
+ accessToken: tokenResponse.accessToken,
2192
+ refreshToken: tokenResponse.refreshToken,
2193
+ expiresAt,
2194
+ email,
2195
+ userId,
2196
+ runtimeTarget: getActiveRuntimeTarget()
2197
+ };
2198
+ const dir = path13.dirname(this.oauthSessionFilePath);
2199
+ if (!fs4.existsSync(dir)) {
2200
+ fs4.mkdirSync(dir, { recursive: true });
2016
2201
  }
2202
+ fs4.writeFileSync(this.oauthSessionFilePath, JSON.stringify(storedAuth, null, 2), {
2203
+ encoding: "utf-8",
2204
+ mode: 384
2205
+ });
2206
+ logger6.info("Auth stored successfully", { email, expiresAt });
2017
2207
  }
2018
- // ========================================
2019
- // Run Results
2020
- // ========================================
2021
2208
  /**
2022
- * List all run results.
2209
+ * Load stored authentication for the active runtime target.
2210
+ * @returns Stored auth, or null when it is absent, unreadable, or was issued for another target.
2023
2211
  */
2024
- listRunResults() {
2212
+ loadStoredAuth() {
2213
+ const logger6 = getLogger();
2214
+ if (!fs4.existsSync(this.oauthSessionFilePath)) {
2215
+ return null;
2216
+ }
2025
2217
  try {
2026
- const files = fs6.readdirSync(this.runResultsDir).filter((f) => f.endsWith(".json"));
2027
- const results = [];
2028
- for (const file of files) {
2029
- try {
2030
- const content = fs6.readFileSync(path11.join(this.runResultsDir, file), "utf-8");
2031
- results.push(JSON.parse(content));
2032
- } catch {
2033
- logger3.warn("Failed to read run result file", { file });
2034
- }
2218
+ const content = fs4.readFileSync(this.oauthSessionFilePath, "utf-8");
2219
+ const storedAuth = JSON.parse(content);
2220
+ const activeRuntimeTarget = getActiveRuntimeTarget();
2221
+ const config = getConfig();
2222
+ const expectedAuth0Domain = config.localQa.auth0.domain;
2223
+ const tokenIssuer = extractJwtIssuer(storedAuth.accessToken);
2224
+ if (!isStoredAuthForRuntimeTarget({
2225
+ storedRuntimeTarget: storedAuth.runtimeTarget,
2226
+ activeRuntimeTarget,
2227
+ tokenIssuer,
2228
+ expectedAuth0Domain
2229
+ })) {
2230
+ logger6.warn("Ignoring stored auth issued for a different runtime target", {
2231
+ storedRuntimeTarget: storedAuth.runtimeTarget ?? "unrecorded",
2232
+ activeRuntimeTarget,
2233
+ tokenIssuer: tokenIssuer ?? "undecodable",
2234
+ expectedAuth0Domain
2235
+ });
2236
+ return null;
2035
2237
  }
2036
- return results.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
2037
- } catch {
2038
- return [];
2238
+ return storedAuth;
2239
+ } catch (error) {
2240
+ logger6.error("Failed to load stored auth", {
2241
+ error: error instanceof Error ? error.message : String(error)
2242
+ });
2243
+ return null;
2039
2244
  }
2040
2245
  }
2041
2246
  /**
2042
- * Get a run result by ID.
2247
+ * Get the current access token (if valid).
2248
+ * Does not auto-refresh - use getValidAccessToken() for that.
2043
2249
  */
2044
- getRunResult(runId) {
2045
- const filePath = path11.join(this.runResultsDir, `${runId}.json`);
2046
- if (!fs6.existsSync(filePath)) {
2047
- return void 0;
2250
+ getAccessToken() {
2251
+ const storedAuth = this.loadStoredAuth();
2252
+ if (!storedAuth) {
2253
+ return null;
2048
2254
  }
2049
- try {
2050
- const content = fs6.readFileSync(filePath, "utf-8");
2051
- return JSON.parse(content);
2052
- } catch {
2053
- return void 0;
2255
+ const now = /* @__PURE__ */ new Date();
2256
+ const expiresAt = new Date(storedAuth.expiresAt);
2257
+ if (now >= expiresAt) {
2258
+ return null;
2054
2259
  }
2260
+ return storedAuth.accessToken;
2055
2261
  }
2056
2262
  /**
2057
- * Save a run result.
2058
- */
2059
- saveRunResult(result) {
2060
- const filePath = path11.join(this.runResultsDir, `${result.id}.json`);
2061
- fs6.writeFileSync(filePath, JSON.stringify(result, null, 2));
2062
- }
2063
- /**
2064
- * Create a new run result.
2065
- */
2066
- createRunResult(params) {
2067
- const now = (/* @__PURE__ */ new Date()).toISOString();
2068
- const id = randomUUID();
2069
- const result = {
2070
- id,
2071
- runType: params.runType,
2072
- status: "pending",
2073
- cloudTestCaseId: params.cloudTestCaseId,
2074
- projectId: params.projectId,
2075
- useCaseId: params.useCaseId,
2076
- localUrl: params.localUrl,
2077
- productionUrl: params.productionUrl,
2078
- localExecutionContext: params.localExecutionContext,
2079
- createdAt: now,
2080
- updatedAt: now
2081
- };
2082
- this.saveRunResult(result);
2083
- return result;
2084
- }
2085
- /**
2086
- * Update a run result.
2263
+ * Check if the access token is expired or about to expire.
2264
+ * Uses a 5-minute buffer for safety.
2087
2265
  */
2088
- updateRunResult(runId, updates) {
2089
- const result = this.getRunResult(runId);
2090
- if (!result) {
2091
- return void 0;
2266
+ isAccessTokenExpired() {
2267
+ const storedAuth = this.loadStoredAuth();
2268
+ if (!storedAuth) {
2269
+ return true;
2092
2270
  }
2093
- const updated = {
2094
- ...result,
2095
- ...updates,
2096
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2097
- };
2098
- this.saveRunResult(updated);
2099
- return updated;
2271
+ const now = /* @__PURE__ */ new Date();
2272
+ const expiresAt = new Date(storedAuth.expiresAt);
2273
+ const bufferMs = 5 * 60 * 1e3;
2274
+ return now.getTime() >= expiresAt.getTime() - bufferMs;
2100
2275
  }
2101
- // ========================================
2102
- // Test Scripts
2103
- // ========================================
2104
2276
  /**
2105
- * List all test scripts.
2277
+ * Refresh the access token using the stored refresh token.
2278
+ * @returns New access token or null if refresh failed.
2106
2279
  */
2107
- listTestScripts() {
2280
+ async refreshAccessToken() {
2281
+ const logger6 = getLogger();
2282
+ const storedAuth = this.loadStoredAuth();
2283
+ if (!storedAuth?.refreshToken) {
2284
+ logger6.debug("No refresh token available");
2285
+ return null;
2286
+ }
2287
+ const config = getConfig();
2288
+ const { domain, clientId } = config.localQa.auth0;
2289
+ logger6.info("Refreshing access token");
2290
+ const url = `https://${domain}/oauth/token`;
2291
+ const body = new URLSearchParams({
2292
+ grant_type: "refresh_token",
2293
+ client_id: clientId,
2294
+ refresh_token: storedAuth.refreshToken
2295
+ });
2108
2296
  try {
2109
- const files = fs6.readdirSync(this.testScriptsDir).filter((f) => f.endsWith(".json"));
2110
- const scripts = [];
2111
- for (const file of files) {
2112
- try {
2113
- const content = fs6.readFileSync(path11.join(this.testScriptsDir, file), "utf-8");
2114
- scripts.push(JSON.parse(content));
2115
- } catch {
2116
- logger3.warn("Failed to read test script file", { file });
2117
- }
2297
+ const response = await fetch(url, {
2298
+ method: "POST",
2299
+ headers: {
2300
+ "Content-Type": "application/x-www-form-urlencoded"
2301
+ },
2302
+ body: body.toString()
2303
+ });
2304
+ if (!response.ok) {
2305
+ const errorText = await response.text();
2306
+ logger6.error("Token refresh failed", {
2307
+ status: response.status,
2308
+ error: errorText
2309
+ });
2310
+ return null;
2118
2311
  }
2119
- return scripts.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
2120
- } catch {
2121
- return [];
2312
+ const tokenData = await response.json();
2313
+ const newExpiresAt = new Date(Date.now() + tokenData.expires_in * 1e3).toISOString();
2314
+ const updatedAuth = {
2315
+ accessToken: tokenData.access_token,
2316
+ refreshToken: tokenData.refresh_token ?? storedAuth.refreshToken,
2317
+ expiresAt: newExpiresAt,
2318
+ email: storedAuth.email,
2319
+ userId: storedAuth.userId,
2320
+ runtimeTarget: storedAuth.runtimeTarget ?? getActiveRuntimeTarget()
2321
+ };
2322
+ const dir = path13.dirname(this.oauthSessionFilePath);
2323
+ if (!fs4.existsSync(dir)) {
2324
+ fs4.mkdirSync(dir, { recursive: true });
2325
+ }
2326
+ fs4.writeFileSync(this.oauthSessionFilePath, JSON.stringify(updatedAuth, null, 2), {
2327
+ encoding: "utf-8",
2328
+ mode: 384
2329
+ });
2330
+ logger6.info("Access token refreshed", { expiresAt: newExpiresAt });
2331
+ return tokenData.access_token;
2332
+ } catch (error) {
2333
+ logger6.error("Token refresh request failed", {
2334
+ error: error instanceof Error ? error.message : String(error)
2335
+ });
2336
+ return null;
2122
2337
  }
2123
2338
  }
2124
2339
  /**
2125
- * Get a test script by ID.
2340
+ * Get a valid access token, refreshing if necessary.
2341
+ * This is the preferred method for getting an access token for API calls.
2342
+ * @returns Valid access token or null if not authenticated or refresh failed.
2126
2343
  */
2127
- getTestScript(testScriptId) {
2128
- const filePath = path11.join(this.testScriptsDir, `${testScriptId}.json`);
2129
- if (!fs6.existsSync(filePath)) {
2130
- return void 0;
2344
+ async getValidAccessToken() {
2345
+ const logger6 = getLogger();
2346
+ const storedAuth = this.loadStoredAuth();
2347
+ if (!storedAuth) {
2348
+ logger6.debug("No stored auth, cannot get valid token");
2349
+ return null;
2131
2350
  }
2132
- try {
2133
- const content = fs6.readFileSync(filePath, "utf-8");
2134
- return JSON.parse(content);
2135
- } catch {
2136
- return void 0;
2351
+ const now = /* @__PURE__ */ new Date();
2352
+ const expiresAt = new Date(storedAuth.expiresAt);
2353
+ const isStrictlyExpired = now >= expiresAt;
2354
+ if (!isStrictlyExpired && !this.isAccessTokenExpired()) {
2355
+ return storedAuth.accessToken;
2137
2356
  }
2357
+ if (!isStrictlyExpired) {
2358
+ logger6.debug("Access token in buffer zone, attempting proactive refresh");
2359
+ } else {
2360
+ logger6.info("Access token expired, attempting refresh");
2361
+ }
2362
+ const refreshedToken = await this.refreshAccessToken();
2363
+ if (refreshedToken) {
2364
+ return refreshedToken;
2365
+ }
2366
+ if (!isStrictlyExpired) {
2367
+ logger6.warn("Token refresh failed, but token not yet expired - using existing token");
2368
+ return storedAuth.accessToken;
2369
+ }
2370
+ logger6.warn("Token refresh failed and token is expired, user needs to re-authenticate");
2371
+ return null;
2138
2372
  }
2139
2373
  /**
2140
- * Save a test script.
2141
- */
2142
- saveTestScript(script) {
2143
- const filePath = path11.join(this.testScriptsDir, `${script.id}.json`);
2144
- fs6.writeFileSync(filePath, JSON.stringify(script, null, 2));
2145
- }
2146
- /**
2147
- * Create a new test script.
2148
- */
2149
- createTestScript(params) {
2150
- const now = (/* @__PURE__ */ new Date()).toISOString();
2151
- const id = randomUUID();
2152
- const script = {
2153
- id,
2154
- name: params.name,
2155
- url: params.url,
2156
- status: "pending",
2157
- cloudTestCaseId: params.cloudTestCaseId,
2158
- goal: params.goal,
2159
- createdAt: now,
2160
- updatedAt: now
2161
- };
2162
- this.saveTestScript(script);
2163
- return script;
2164
- }
2165
- /**
2166
- * Update a test script.
2374
+ * Clear stored authentication (logout).
2167
2375
  */
2168
- updateTestScript(testScriptId, updates) {
2169
- const script = this.getTestScript(testScriptId);
2170
- if (!script) {
2171
- return void 0;
2376
+ logout() {
2377
+ const logger6 = getLogger();
2378
+ if (!fs4.existsSync(this.oauthSessionFilePath)) {
2379
+ logger6.debug("No auth to clear");
2380
+ return false;
2381
+ }
2382
+ try {
2383
+ fs4.unlinkSync(this.oauthSessionFilePath);
2384
+ logger6.info("Auth cleared successfully");
2385
+ return true;
2386
+ } catch (error) {
2387
+ logger6.error("Failed to clear auth", {
2388
+ error: error instanceof Error ? error.message : String(error)
2389
+ });
2390
+ return false;
2172
2391
  }
2173
- const updated = {
2174
- ...script,
2175
- ...updates,
2176
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2177
- };
2178
- this.saveTestScript(updated);
2179
- return updated;
2180
2392
  }
2181
2393
  };
2182
- var instance = null;
2183
- function getRunResultStorageService() {
2184
- if (!instance) {
2185
- instance = new RunResultStorageService();
2186
- }
2187
- return instance;
2394
+ var serviceInstance2 = null;
2395
+ function getAuthService() {
2396
+ serviceInstance2 ??= new AuthService();
2397
+ return serviceInstance2;
2188
2398
  }
2189
- function resetRunResultStorageService() {
2190
- instance = null;
2399
+ function resetAuthService() {
2400
+ serviceInstance2 = null;
2401
+ }
2402
+ function sleep(params) {
2403
+ return new Promise((resolve6) => {
2404
+ setTimeout(() => {
2405
+ resolve6();
2406
+ }, params.durationMs);
2407
+ });
2191
2408
  }
2192
2409
 
2193
2410
  // packages/mcps/src/shared/llm-env-constants.ts
@@ -2406,13 +2623,13 @@ var PREFERENCES_SCHEMA = {
2406
2623
 
2407
2624
  // packages/mcps/src/shared/llm-env-service.ts
2408
2625
  function resolveLlmEnvOverrides(dataDirOverride2) {
2409
- const filePath = path11.join(getDataDir(), PREFERENCES_FILE_NAME);
2626
+ const filePath = path13.join(getDataDir(), PREFERENCES_FILE_NAME);
2410
2627
  let block;
2411
2628
  try {
2412
- if (!fs6.existsSync(filePath)) {
2629
+ if (!fs4.existsSync(filePath)) {
2413
2630
  return {};
2414
2631
  }
2415
- const raw = JSON.parse(fs6.readFileSync(filePath, "utf-8"));
2632
+ const raw = JSON.parse(fs4.readFileSync(filePath, "utf-8"));
2416
2633
  block = raw[LLM_ENV_PREFERENCE_KEY];
2417
2634
  } catch {
2418
2635
  return {};
@@ -2647,13 +2864,13 @@ var MODIFY_LOCK_STALE_MS = 5e3;
2647
2864
 
2648
2865
  // packages/mcps/src/mcp/local/services/local-execution-lock.ts
2649
2866
  function lockFilePath() {
2650
- return path11.join(getDataDir(), LOCK_FILE_NAME);
2867
+ return path13.join(getDataDir(), LOCK_FILE_NAME);
2651
2868
  }
2652
2869
  function modifyLockPath() {
2653
2870
  return lockFilePath() + MODIFY_LOCK_SUFFIX;
2654
2871
  }
2655
2872
  function normalizeCwd(cwd) {
2656
- const resolved = path11.resolve(cwd);
2873
+ const resolved = path13.resolve(cwd);
2657
2874
  return os4.platform() === "win32" ? resolved.toLowerCase() : resolved;
2658
2875
  }
2659
2876
  function pidIsAlive(pid) {
@@ -2669,7 +2886,7 @@ function pidIsAlive(pid) {
2669
2886
  }
2670
2887
  async function readState() {
2671
2888
  try {
2672
- const raw = await fs10.readFile(lockFilePath(), "utf-8");
2889
+ const raw = await fs12.readFile(lockFilePath(), "utf-8");
2673
2890
  return JSON.parse(raw);
2674
2891
  } catch (error) {
2675
2892
  if (error.code === "ENOENT") return null;
@@ -2677,28 +2894,28 @@ async function readState() {
2677
2894
  }
2678
2895
  }
2679
2896
  async function writeState(state2) {
2680
- await fs10.writeFile(lockFilePath(), JSON.stringify(state2, null, 2), "utf-8");
2897
+ await fs12.writeFile(lockFilePath(), JSON.stringify(state2, null, 2), "utf-8");
2681
2898
  }
2682
2899
  async function deleteStateFile() {
2683
2900
  try {
2684
- await fs10.unlink(lockFilePath());
2901
+ await fs12.unlink(lockFilePath());
2685
2902
  } catch (error) {
2686
2903
  if (error.code !== "ENOENT") throw error;
2687
2904
  }
2688
2905
  }
2689
2906
  async function withModifyLock(fn) {
2690
- await fs10.mkdir(path11.dirname(modifyLockPath()), { recursive: true });
2907
+ await fs12.mkdir(path13.dirname(modifyLockPath()), { recursive: true });
2691
2908
  const modifyPath = modifyLockPath();
2692
2909
  let fileHandle = null;
2693
2910
  while (fileHandle === null) {
2694
2911
  try {
2695
- fileHandle = await fs10.open(modifyPath, "wx");
2912
+ fileHandle = await fs12.open(modifyPath, "wx");
2696
2913
  } catch (error) {
2697
2914
  if (error.code !== "EEXIST") throw error;
2698
2915
  try {
2699
- const stat3 = await fs10.stat(modifyPath);
2916
+ const stat3 = await fs12.stat(modifyPath);
2700
2917
  if (Date.now() - stat3.mtimeMs > MODIFY_LOCK_STALE_MS) {
2701
- await fs10.unlink(modifyPath).catch(() => void 0);
2918
+ await fs12.unlink(modifyPath).catch(() => void 0);
2702
2919
  continue;
2703
2920
  }
2704
2921
  } catch {
@@ -2711,7 +2928,7 @@ async function withModifyLock(fn) {
2711
2928
  return await fn();
2712
2929
  } finally {
2713
2930
  await fileHandle.close().catch(() => void 0);
2714
- await fs10.unlink(modifyPath).catch(() => void 0);
2931
+ await fs12.unlink(modifyPath).catch(() => void 0);
2715
2932
  }
2716
2933
  }
2717
2934
  async function tryAcquireOnce(params) {
@@ -2850,12 +3067,12 @@ function extractStudioCloudRefs(studioReturnedResult) {
2850
3067
  return refs;
2851
3068
  }
2852
3069
  async function readCloudRefsFile(inputFilePath) {
2853
- const cloudRefsPath = path11.join(
2854
- path11.dirname(inputFilePath),
2855
- `cloudrefs_${path11.basename(inputFilePath)}`
3070
+ const cloudRefsPath = path13.join(
3071
+ path13.dirname(inputFilePath),
3072
+ `cloudrefs_${path13.basename(inputFilePath)}`
2856
3073
  );
2857
3074
  try {
2858
- return JSON.parse(await fs10.readFile(cloudRefsPath, "utf-8"));
3075
+ return JSON.parse(await fs12.readFile(cloudRefsPath, "utf-8"));
2859
3076
  } catch {
2860
3077
  return void 0;
2861
3078
  }
@@ -2874,15 +3091,15 @@ function getAuthenticatedUserId() {
2874
3091
  }
2875
3092
  async function findPackageJsonAsync() {
2876
3093
  const currentFileUrl = import.meta.url;
2877
- const currentDir = path11.dirname(fileURLToPath(currentFileUrl));
3094
+ const currentDir = path13.dirname(fileURLToPath(currentFileUrl));
2878
3095
  const candidatePaths = [
2879
- path11.resolve(currentDir, "../../../package.json"),
2880
- path11.resolve(currentDir, "../package.json"),
2881
- path11.resolve(currentDir, "../../package.json")
3096
+ path13.resolve(currentDir, "../../../package.json"),
3097
+ path13.resolve(currentDir, "../package.json"),
3098
+ path13.resolve(currentDir, "../../package.json")
2882
3099
  ];
2883
3100
  for (const candidatePath of candidatePaths) {
2884
3101
  try {
2885
- const packageJsonRaw = await fs10.readFile(candidatePath, "utf-8");
3102
+ const packageJsonRaw = await fs12.readFile(candidatePath, "utf-8");
2886
3103
  const packageJson = JSON.parse(packageJsonRaw);
2887
3104
  if (packageJson.name === "@muggleai/works" && packageJson.version && packageJson.muggleConfig?.electronAppVersion) {
2888
3105
  return {
@@ -2928,20 +3145,20 @@ function buildStudioAuthContent() {
2928
3145
  }
2929
3146
  async function ensureTempDir() {
2930
3147
  const config = getConfig();
2931
- const tempDir = path11.join(config.localQa.dataDir, "temp");
2932
- await fs10.mkdir(tempDir, { recursive: true });
3148
+ const tempDir = path13.join(config.localQa.dataDir, "temp");
3149
+ await fs12.mkdir(tempDir, { recursive: true });
2933
3150
  return tempDir;
2934
3151
  }
2935
3152
  async function writeTempFile(params) {
2936
3153
  const tempDir = await ensureTempDir();
2937
- const filePath = path11.join(tempDir, params.filename);
2938
- await fs10.writeFile(filePath, JSON.stringify(params.data, null, 2));
3154
+ const filePath = path13.join(tempDir, params.filename);
3155
+ await fs12.writeFile(filePath, JSON.stringify(params.data, null, 2));
2939
3156
  return filePath;
2940
3157
  }
2941
3158
  async function cleanupTempFiles(params) {
2942
3159
  for (const filePath of params.filePaths) {
2943
3160
  try {
2944
- await fs10.unlink(filePath);
3161
+ await fs12.unlink(filePath);
2945
3162
  } catch {
2946
3163
  }
2947
3164
  }
@@ -2949,12 +3166,12 @@ async function cleanupTempFiles(params) {
2949
3166
  async function moveResultsToArtifacts(params) {
2950
3167
  const storageService = getStorageService();
2951
3168
  const sessionsDir = storageService.getSessionsDir();
2952
- const artifactsDir = path11.join(sessionsDir, params.runId);
2953
- const actionScriptPath = path11.join(artifactsDir, "action-script.json");
2954
- await fs10.mkdir(artifactsDir, { recursive: true });
2955
- await fs10.copyFile(params.generatedScriptPath, actionScriptPath);
3169
+ const artifactsDir = path13.join(sessionsDir, params.runId);
3170
+ const actionScriptPath = path13.join(artifactsDir, "action-script.json");
3171
+ await fs12.mkdir(artifactsDir, { recursive: true });
3172
+ await fs12.copyFile(params.generatedScriptPath, actionScriptPath);
2956
3173
  try {
2957
- await fs10.unlink(params.generatedScriptPath);
3174
+ await fs12.unlink(params.generatedScriptPath);
2958
3175
  } catch {
2959
3176
  }
2960
3177
  return artifactsDir;
@@ -2962,13 +3179,13 @@ async function moveResultsToArtifacts(params) {
2962
3179
  async function writeExecutionLogs(params) {
2963
3180
  const storageService = getStorageService();
2964
3181
  const sessionsDir = storageService.getSessionsDir();
2965
- const artifactsDir = path11.join(sessionsDir, params.runId);
2966
- await fs10.mkdir(artifactsDir, { recursive: true });
2967
- const stdoutPath = path11.join(artifactsDir, "stdout.log");
2968
- const stderrPath = path11.join(artifactsDir, "stderr.log");
3182
+ const artifactsDir = path13.join(sessionsDir, params.runId);
3183
+ await fs12.mkdir(artifactsDir, { recursive: true });
3184
+ const stdoutPath = path13.join(artifactsDir, "stdout.log");
3185
+ const stderrPath = path13.join(artifactsDir, "stderr.log");
2969
3186
  await Promise.all([
2970
- fs10.writeFile(stdoutPath, params.stdout, "utf-8"),
2971
- fs10.writeFile(stderrPath, params.stderr, "utf-8")
3187
+ fs12.writeFile(stdoutPath, params.stdout, "utf-8"),
3188
+ fs12.writeFile(stderrPath, params.stderr, "utf-8")
2972
3189
  ]);
2973
3190
  logger4.info("Wrote execution logs to artifacts directory", {
2974
3191
  runId: params.runId,
@@ -2978,10 +3195,10 @@ async function writeExecutionLogs(params) {
2978
3195
  async function preserveElectronRuntimeArtifacts(params) {
2979
3196
  try {
2980
3197
  const tempDir = await ensureTempDir();
2981
- const markerPath = path11.join(tempDir, `${params.runId}_runtime_dir.txt`);
3198
+ const markerPath = path13.join(tempDir, `${params.runId}_runtime_dir.txt`);
2982
3199
  let runtimeDir = null;
2983
3200
  try {
2984
- const marker = await fs10.readFile(markerPath, "utf-8");
3201
+ const marker = await fs12.readFile(markerPath, "utf-8");
2985
3202
  const trimmed = marker.trim();
2986
3203
  if (trimmed.length > 0) runtimeDir = trimmed;
2987
3204
  } catch (markerReadError) {
@@ -3003,25 +3220,25 @@ async function preserveElectronRuntimeArtifacts(params) {
3003
3220
  return;
3004
3221
  }
3005
3222
  try {
3006
- await fs10.access(runtimeDir);
3223
+ await fs12.access(runtimeDir);
3007
3224
  } catch {
3008
3225
  logger4.warn("Electron runtime dir referenced but missing on disk", {
3009
3226
  runId: params.runId,
3010
3227
  runtimeDir
3011
3228
  });
3012
- await fs10.unlink(markerPath).catch(() => void 0);
3229
+ await fs12.unlink(markerPath).catch(() => void 0);
3013
3230
  return;
3014
3231
  }
3015
3232
  const storage = getStorageService();
3016
- const destDir = path11.join(storage.getSessionsDir(), params.runId, "electron-runtime");
3017
- await fs10.mkdir(destDir, { recursive: true });
3018
- await fs10.cp(runtimeDir, destDir, { recursive: true });
3233
+ const destDir = path13.join(storage.getSessionsDir(), params.runId, "electron-runtime");
3234
+ await fs12.mkdir(destDir, { recursive: true });
3235
+ await fs12.cp(runtimeDir, destDir, { recursive: true });
3019
3236
  logger4.info("Preserved Electron runtime artifacts", {
3020
3237
  runId: params.runId,
3021
3238
  sourceDir: runtimeDir,
3022
3239
  destDir
3023
3240
  });
3024
- await fs10.unlink(markerPath).catch(() => void 0);
3241
+ await fs12.unlink(markerPath).catch(() => void 0);
3025
3242
  } catch (preserveError) {
3026
3243
  logger4.warn("Failed to preserve Electron runtime artifacts", {
3027
3244
  runId: params.runId,
@@ -3030,14 +3247,14 @@ async function preserveElectronRuntimeArtifacts(params) {
3030
3247
  }
3031
3248
  }
3032
3249
  async function scanForRuntimeDirByMtime(params) {
3033
- const runtimeRoot = path11.join(params.electronCwd, "user_data", "runtime");
3250
+ const runtimeRoot = path13.join(params.electronCwd, "user_data", "runtime");
3034
3251
  try {
3035
- const entries = await fs10.readdir(runtimeRoot, { withFileTypes: true });
3252
+ const entries = await fs12.readdir(runtimeRoot, { withFileTypes: true });
3036
3253
  let bestCandidate = null;
3037
3254
  for (const entry of entries) {
3038
3255
  if (!entry.isDirectory()) continue;
3039
- const fullPath = path11.join(runtimeRoot, entry.name);
3040
- const stat3 = await fs10.stat(fullPath);
3256
+ const fullPath = path13.join(runtimeRoot, entry.name);
3257
+ const stat3 = await fs12.stat(fullPath);
3041
3258
  if (stat3.mtimeMs < params.spawnAtMs) continue;
3042
3259
  if (bestCandidate === null || stat3.mtimeMs > bestCandidate.mtimeMs) {
3043
3260
  bestCandidate = { path: fullPath, mtimeMs: stat3.mtimeMs };
@@ -3093,7 +3310,7 @@ async function executeElectronAppAsync(params) {
3093
3310
  spawnArgs,
3094
3311
  llmEnvOverrideNames
3095
3312
  });
3096
- const electronCwd = path11.dirname(electronAppPath);
3313
+ const electronCwd = path13.dirname(electronAppPath);
3097
3314
  const child = spawn(electronAppPath, spawnArgs, {
3098
3315
  stdio: ["ignore", "pipe", "pipe"],
3099
3316
  env: electronEnv,
@@ -3276,17 +3493,17 @@ ${executionResult.stdout}
3276
3493
  STDERR:
3277
3494
  ${executionResult.stderr}`;
3278
3495
  const sessionsDir = getStorageService().getSessionsDir();
3279
- const artifactsDir2 = path11.join(sessionsDir, runId);
3280
- const generatedScriptPath2 = path11.join(
3281
- path11.dirname(inputFilePath),
3282
- `gen_${path11.basename(inputFilePath)}`
3496
+ const artifactsDir2 = path13.join(sessionsDir, runId);
3497
+ const generatedScriptPath2 = path13.join(
3498
+ path13.dirname(inputFilePath),
3499
+ `gen_${path13.basename(inputFilePath)}`
3283
3500
  );
3284
3501
  let failedStudioResult;
3285
3502
  try {
3286
- const failedScriptRaw = await fs10.readFile(generatedScriptPath2, "utf-8");
3503
+ const failedScriptRaw = await fs12.readFile(generatedScriptPath2, "utf-8");
3287
3504
  failedStudioResult = JSON.parse(failedScriptRaw);
3288
- await fs10.copyFile(generatedScriptPath2, path11.join(artifactsDir2, "action-script.json"));
3289
- await fs10.unlink(generatedScriptPath2).catch(() => {
3505
+ await fs12.copyFile(generatedScriptPath2, path13.join(artifactsDir2, "action-script.json"));
3506
+ await fs12.unlink(generatedScriptPath2).catch(() => {
3290
3507
  });
3291
3508
  } catch {
3292
3509
  }
@@ -3315,11 +3532,11 @@ ${executionResult.stderr}`;
3315
3532
  errorMessage: failureMessage
3316
3533
  };
3317
3534
  }
3318
- const generatedScriptPath = path11.join(
3319
- path11.dirname(inputFilePath),
3320
- `gen_${path11.basename(inputFilePath)}`
3535
+ const generatedScriptPath = path13.join(
3536
+ path13.dirname(inputFilePath),
3537
+ `gen_${path13.basename(inputFilePath)}`
3321
3538
  );
3322
- const generatedScriptRaw = await fs10.readFile(generatedScriptPath, "utf-8");
3539
+ const generatedScriptRaw = await fs12.readFile(generatedScriptPath, "utf-8");
3323
3540
  const generatedScript = JSON.parse(generatedScriptRaw);
3324
3541
  const generatedSteps = generatedScript.steps;
3325
3542
  if (!Array.isArray(generatedSteps)) {
@@ -3463,7 +3680,7 @@ STDOUT:
3463
3680
  ${executionResult.stdout}
3464
3681
  STDERR:
3465
3682
  ${executionResult.stderr}`;
3466
- const artifactsDir2 = path11.join(getStorageService().getSessionsDir(), runId);
3683
+ const artifactsDir2 = path13.join(getStorageService().getSessionsDir(), runId);
3467
3684
  storage.updateRunResult(runId, {
3468
3685
  status: "failed",
3469
3686
  executionTimeMs,
@@ -3481,7 +3698,7 @@ ${executionResult.stderr}`;
3481
3698
  errorMessage: failureMessage
3482
3699
  };
3483
3700
  }
3484
- const artifactsDir = path11.join(getStorageService().getSessionsDir(), runId);
3701
+ const artifactsDir = path13.join(getStorageService().getSessionsDir(), runId);
3485
3702
  const replayStudioResult = await readCloudRefsFile(inputFilePath);
3486
3703
  const replayStudioCloudRefs = extractStudioCloudRefs(replayStudioResult);
3487
3704
  storage.updateRunResult(runId, {
@@ -4011,7 +4228,7 @@ var PREFS_FILENAME = "preferences.json";
4011
4228
  var OAUTH_SESSION_FILENAME = "oauth-session.json";
4012
4229
  var RELEASE_MANIFEST_FILENAME = "release-manifest.json";
4013
4230
  function getDataDir3() {
4014
- return path11.join(os4.homedir(), DATA_DIR_NAME2);
4231
+ return path13.join(os4.homedir(), DATA_DIR_NAME2);
4015
4232
  }
4016
4233
 
4017
4234
  var cachedInstallId2;
@@ -4076,9 +4293,9 @@ function isValid3(value) {
4076
4293
  function readReleaseManifest2() {
4077
4294
  if (cachedManifest2 !== void 0)
4078
4295
  return cachedManifest2;
4079
- const path17 = MANIFEST_PATH2;
4296
+ const path19 = MANIFEST_PATH2;
4080
4297
  try {
4081
- const raw = readFileSync(path17, "utf8");
4298
+ const raw = readFileSync(path19, "utf8");
4082
4299
  const parsed = JSON.parse(raw);
4083
4300
  cachedManifest2 = isValid3(parsed) ? parsed : DEV_MANIFEST2;
4084
4301
  return cachedManifest2;
@@ -4845,17 +5062,17 @@ var PromptServiceClient = class {
4845
5062
  * @param path - Path to validate.
4846
5063
  * @throws GatewayError if path is not allowed.
4847
5064
  */
4848
- validatePath(path17) {
4849
- const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) => path17.startsWith(prefix));
5065
+ validatePath(path19) {
5066
+ const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) => path19.startsWith(prefix));
4850
5067
  if (!isAllowed) {
4851
5068
  const logger6 = getLogger();
4852
5069
  logger6.error("Path not in allowlist", {
4853
- path: path17,
5070
+ path: path19,
4854
5071
  allowedPrefixes: ALLOWED_UPSTREAM_PREFIXES
4855
5072
  });
4856
5073
  throw new GatewayError({
4857
5074
  code: "FORBIDDEN" /* FORBIDDEN */,
4858
- message: `Path '${path17}' is not allowed`
5075
+ message: `Path '${path19}' is not allowed`
4859
5076
  });
4860
5077
  }
4861
5078
  }
@@ -6935,6 +7152,9 @@ var RunResultListInputSchema = z.object({
6935
7152
  var RunResultGetInputSchema = z.object({
6936
7153
  runId: MuggleEntityIdSchema.describe("Run result ID (UUID) to retrieve")
6937
7154
  });
7155
+ var RunStepsGetInputSchema = z.object({
7156
+ runId: z.string().min(1).optional().describe("Run id, or a unique prefix of one. Omit to read the most recent run.")
7157
+ });
6938
7158
  var TestScriptListInputSchema2 = z.object({
6939
7159
  cloudTestCaseId: MuggleEntityIdSchema.optional().describe("Optional cloud test case ID (UUID) to filter by")
6940
7160
  });
@@ -7028,11 +7248,11 @@ var TELEMETRY_DIR_NAME = "telemetry";
7028
7248
  var FAILURE_EVENTS_FILE_NAME = "failure-events.jsonl";
7029
7249
  function appendFailureEvent(record) {
7030
7250
  try {
7031
- const dir = path11.join(getDataDir(), TELEMETRY_DIR_NAME);
7032
- fs6.mkdirSync(dir, { recursive: true });
7033
- const filePath = path11.join(dir, FAILURE_EVENTS_FILE_NAME);
7251
+ const dir = path13.join(getDataDir(), TELEMETRY_DIR_NAME);
7252
+ fs4.mkdirSync(dir, { recursive: true });
7253
+ const filePath = path13.join(dir, FAILURE_EVENTS_FILE_NAME);
7034
7254
  const full = { ts: (/* @__PURE__ */ new Date()).toISOString(), ...record };
7035
- fs6.appendFileSync(filePath, `${JSON.stringify(full)}
7255
+ fs4.appendFileSync(filePath, `${JSON.stringify(full)}
7036
7256
  `, "utf-8");
7037
7257
  } catch (error) {
7038
7258
  getLogger().warn("Failed to append failure event", {
@@ -7051,17 +7271,17 @@ var ProjectPreferencesReconcileOutcome = /* @__PURE__ */ ((ProjectPreferencesRec
7051
7271
 
7052
7272
  // packages/mcps/src/shared/preferences-service.ts
7053
7273
  function getGlobalPreferencesFilePath(dataDirOverride2) {
7054
- return path11.join(dataDirOverride2 ?? getDataDir(), PREFERENCES_FILE_NAME);
7274
+ return path13.join(dataDirOverride2 ?? getDataDir(), PREFERENCES_FILE_NAME);
7055
7275
  }
7056
7276
  function isFirstRun(dataDirOverride2) {
7057
- return !fs6.existsSync(getGlobalPreferencesFilePath(dataDirOverride2));
7277
+ return !fs4.existsSync(getGlobalPreferencesFilePath(dataDirOverride2));
7058
7278
  }
7059
7279
  function readPreferencesFile(filePath) {
7060
7280
  try {
7061
- if (!fs6.existsSync(filePath)) {
7281
+ if (!fs4.existsSync(filePath)) {
7062
7282
  return {};
7063
7283
  }
7064
- const raw = JSON.parse(fs6.readFileSync(filePath, "utf-8"));
7284
+ const raw = JSON.parse(fs4.readFileSync(filePath, "utf-8"));
7065
7285
  return raw.preferences ?? {};
7066
7286
  } catch (error) {
7067
7287
  getLogger().warn("Failed to read preferences file", {
@@ -7077,15 +7297,15 @@ function writePreferencesFile(filePath, prefs) {
7077
7297
  version: PREFERENCES_VERSION,
7078
7298
  preferences: prefs
7079
7299
  };
7080
- fs6.writeFileSync(filePath, `${JSON.stringify(file, null, 2)}
7300
+ fs4.writeFileSync(filePath, `${JSON.stringify(file, null, 2)}
7081
7301
  `, "utf-8");
7082
7302
  }
7083
7303
  function readRawPreferencesFile(filePath) {
7084
7304
  try {
7085
- if (!fs6.existsSync(filePath)) {
7305
+ if (!fs4.existsSync(filePath)) {
7086
7306
  return {};
7087
7307
  }
7088
- return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
7308
+ return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
7089
7309
  } catch {
7090
7310
  return {};
7091
7311
  }
@@ -7096,8 +7316,8 @@ function resolvePreferences(dataDirOverride2) {
7096
7316
  }
7097
7317
  function writePreferences2(prefs, dataDirOverride2) {
7098
7318
  const dir = dataDirOverride2 ?? getDataDir();
7099
- fs6.mkdirSync(dir, { recursive: true });
7100
- const filePath = path11.join(dir, PREFERENCES_FILE_NAME);
7319
+ fs4.mkdirSync(dir, { recursive: true });
7320
+ const filePath = path13.join(dir, PREFERENCES_FILE_NAME);
7101
7321
  const existing = readPreferencesFile(filePath);
7102
7322
  writePreferencesFile(filePath, { ...existing, ...prefs });
7103
7323
  }
@@ -7110,8 +7330,8 @@ function readPreferencesMetadata(dataDirOverride2) {
7110
7330
  }
7111
7331
  function writePreferencesWithMetadata(prefs, metadata, dataDirOverride2) {
7112
7332
  const dir = dataDirOverride2 ?? getDataDir();
7113
- fs6.mkdirSync(dir, { recursive: true });
7114
- const filePath = path11.join(dir, PREFERENCES_FILE_NAME);
7333
+ fs4.mkdirSync(dir, { recursive: true });
7334
+ const filePath = path13.join(dir, PREFERENCES_FILE_NAME);
7115
7335
  const existing = readRawPreferencesFile(filePath);
7116
7336
  const file = {
7117
7337
  ...existing,
@@ -7119,12 +7339,12 @@ function writePreferencesWithMetadata(prefs, metadata, dataDirOverride2) {
7119
7339
  version: PREFERENCES_VERSION,
7120
7340
  preferences: { ...existing.preferences ?? {}, ...prefs }
7121
7341
  };
7122
- fs6.writeFileSync(filePath, `${JSON.stringify(file, null, 2)}
7342
+ fs4.writeFileSync(filePath, `${JSON.stringify(file, null, 2)}
7123
7343
  `, "utf-8");
7124
7344
  }
7125
7345
  function resetPreference(key, dataDirOverride2) {
7126
7346
  const filePath = getGlobalPreferencesFilePath(dataDirOverride2);
7127
- if (!fs6.existsSync(filePath)) {
7347
+ if (!fs4.existsSync(filePath)) {
7128
7348
  return;
7129
7349
  }
7130
7350
  if (!key) {
@@ -7136,7 +7356,7 @@ function resetPreference(key, dataDirOverride2) {
7136
7356
  writePreferencesFile(filePath, existing);
7137
7357
  }
7138
7358
  function reconcileProjectPreferences(cwd, dataDirOverride2) {
7139
- const projectFilePath = path11.join(cwd, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_FILE_NAME);
7359
+ const projectFilePath = path13.join(cwd, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_FILE_NAME);
7140
7360
  const projectPreferences = readPreferencesFile(projectFilePath);
7141
7361
  const projectKeys = Object.keys(projectPreferences);
7142
7362
  if (projectKeys.length === 0) {
@@ -7181,17 +7401,17 @@ var MUGGLE_HOME_DISPLAY_DIR = "~/.muggle-ai";
7181
7401
 
7182
7402
  // packages/mcps/src/shared/cwd-keyed-cache.ts
7183
7403
  function resolveHomeFilePath(cache) {
7184
- return path11.join(getDataDir(), cache.fileName);
7404
+ return path13.join(getDataDir(), cache.fileName);
7185
7405
  }
7186
7406
  function resolveLegacyFilePath(cache, cwd) {
7187
- return path11.join(cwd, LEGACY_CACHE_DIR_NAME, cache.fileName);
7407
+ return path13.join(cwd, LEGACY_CACHE_DIR_NAME, cache.fileName);
7188
7408
  }
7189
7409
  function parseJsonFile(filePath) {
7190
7410
  try {
7191
- if (!fs6.existsSync(filePath)) {
7411
+ if (!fs4.existsSync(filePath)) {
7192
7412
  return null;
7193
7413
  }
7194
- return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
7414
+ return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
7195
7415
  } catch (error) {
7196
7416
  getLogger().warn("Failed to read cache file", {
7197
7417
  path: filePath,
@@ -7205,12 +7425,12 @@ function readHomeEntries(cache) {
7205
7425
  return parsed?.entries ?? {};
7206
7426
  }
7207
7427
  function writeHomeEntries(cache, entries) {
7208
- fs6.mkdirSync(getDataDir(), { recursive: true });
7428
+ fs4.mkdirSync(getDataDir(), { recursive: true });
7209
7429
  const file = {
7210
7430
  version: CWD_KEYED_CACHE_VERSION,
7211
7431
  entries
7212
7432
  };
7213
- fs6.writeFileSync(
7433
+ fs4.writeFileSync(
7214
7434
  resolveHomeFilePath(cache),
7215
7435
  `${JSON.stringify(file, null, 2)}
7216
7436
  `,
@@ -7224,7 +7444,7 @@ function readLegacyEntry(cache, cwd) {
7224
7444
  return parsed?.[cache.legacyEntryKey] ?? null;
7225
7445
  }
7226
7446
  function readCwdEntry(cache, cwd) {
7227
- const cwdKey = path11.resolve(cwd);
7447
+ const cwdKey = path13.resolve(cwd);
7228
7448
  const entries = readHomeEntries(cache);
7229
7449
  const homeEntry = entries[cwdKey];
7230
7450
  if (homeEntry) {
@@ -7246,18 +7466,18 @@ function readCwdEntry(cache, cwd) {
7246
7466
  }
7247
7467
  function writeCwdEntry(cache, cwd, entry) {
7248
7468
  const entries = readHomeEntries(cache);
7249
- writeHomeEntries(cache, { ...entries, [path11.resolve(cwd)]: entry });
7469
+ writeHomeEntries(cache, { ...entries, [path13.resolve(cwd)]: entry });
7250
7470
  }
7251
7471
  function clearCwdEntry(cache, cwd) {
7252
7472
  const entries = readHomeEntries(cache);
7253
- const cwdKey = path11.resolve(cwd);
7473
+ const cwdKey = path13.resolve(cwd);
7254
7474
  if (cwdKey in entries) {
7255
7475
  delete entries[cwdKey];
7256
7476
  writeHomeEntries(cache, entries);
7257
7477
  }
7258
7478
  const legacyFilePath = resolveLegacyFilePath(cache, cwd);
7259
- if (fs6.existsSync(legacyFilePath)) {
7260
- fs6.unlinkSync(legacyFilePath);
7479
+ if (fs4.existsSync(legacyFilePath)) {
7480
+ fs4.unlinkSync(legacyFilePath);
7261
7481
  }
7262
7482
  }
7263
7483
 
@@ -7390,6 +7610,29 @@ var runResultListTool = {
7390
7610
  return { content, isError: false, data: { results } };
7391
7611
  }
7392
7612
  };
7613
+ var runStepsGetTool = {
7614
+ name: "muggle-local-run-steps-get",
7615
+ description: "Read a local run's steps beside the frames they produced, so the run's own verdict can be shown against its own evidence. Returns the ordered steps (action, the agent's explanation, screenshot path) plus the run's status and closing verdict. Takes a run id or a unique prefix; omit it for the most recent run. Read-only \u2014 it never modifies a session. Reach for this after a failed local run instead of parsing action-script.json by hand.",
7616
+ inputSchema: RunStepsGetInputSchema,
7617
+ execute: async (ctx) => {
7618
+ const logger6 = createChildLogger2(ctx.correlationId);
7619
+ logger6.info("Executing muggle-local-run-steps-get");
7620
+ const input = RunStepsGetInputSchema.parse(ctx.input);
7621
+ try {
7622
+ const runView = readRunView(input.runId);
7623
+ return {
7624
+ content: renderRunViewText(runView, { includeHtmlOffer: false }),
7625
+ isError: false,
7626
+ data: { run: runView }
7627
+ };
7628
+ } catch (error) {
7629
+ if (error instanceof RunViewError) {
7630
+ return { content: error.message, isError: true };
7631
+ }
7632
+ throw error;
7633
+ }
7634
+ }
7635
+ };
7393
7636
  var runResultGetTool = {
7394
7637
  name: "muggle-local-run-result-get",
7395
7638
  description: "Get detailed information about a run result including screenshots and action script output.",
@@ -7422,7 +7665,7 @@ var runResultGetTool = {
7422
7665
  const testScript = storage.getTestScript(storedRunResult.testScriptId);
7423
7666
  testScriptSteps = testScript?.actionScript?.length;
7424
7667
  }
7425
- if (storedRunResult.artifactsDir && fs6.existsSync(storedRunResult.artifactsDir)) {
7668
+ if (storedRunResult.artifactsDir && fs4.existsSync(storedRunResult.artifactsDir)) {
7426
7669
  contentParts.push(
7427
7670
  "",
7428
7671
  "### Artifacts (view action script + screenshots)",
@@ -7430,26 +7673,26 @@ var runResultGetTool = {
7430
7673
  `**Location:** \`${storedRunResult.artifactsDir}\``,
7431
7674
  ""
7432
7675
  );
7433
- const actionScriptPath = path11.join(storedRunResult.artifactsDir, "action-script.json");
7434
- const resultsMdPath = path11.join(storedRunResult.artifactsDir, "results.md");
7435
- const screenshotsDir = path11.join(storedRunResult.artifactsDir, "screenshots");
7436
- const stdoutLogPath = path11.join(storedRunResult.artifactsDir, "stdout.log");
7437
- const stderrLogPath = path11.join(storedRunResult.artifactsDir, "stderr.log");
7676
+ const actionScriptPath = path13.join(storedRunResult.artifactsDir, "action-script.json");
7677
+ const resultsMdPath = path13.join(storedRunResult.artifactsDir, "results.md");
7678
+ const screenshotsDir = path13.join(storedRunResult.artifactsDir, "screenshots");
7679
+ const stdoutLogPath = path13.join(storedRunResult.artifactsDir, "stdout.log");
7680
+ const stderrLogPath = path13.join(storedRunResult.artifactsDir, "stderr.log");
7438
7681
  const artifactItems = [];
7439
- if (fs6.existsSync(actionScriptPath)) {
7682
+ if (fs4.existsSync(actionScriptPath)) {
7440
7683
  artifactItems.push("- `action-script.json` \u2014 generated test steps");
7441
7684
  }
7442
- if (fs6.existsSync(resultsMdPath)) {
7685
+ if (fs4.existsSync(resultsMdPath)) {
7443
7686
  artifactItems.push("- `results.md` \u2014 step-by-step report with screenshot links");
7444
7687
  }
7445
- if (fs6.existsSync(screenshotsDir)) {
7446
- const screenshots = fs6.readdirSync(screenshotsDir).filter((f) => /\.(png|jpg|jpeg|webp)$/i.test(f));
7688
+ if (fs4.existsSync(screenshotsDir)) {
7689
+ const screenshots = fs4.readdirSync(screenshotsDir).filter((f) => /\.(png|jpg|jpeg|webp)$/i.test(f));
7447
7690
  artifactItems.push(`- \`screenshots/\` \u2014 ${screenshots.length} image(s)`);
7448
7691
  }
7449
- if (fs6.existsSync(stdoutLogPath)) {
7692
+ if (fs4.existsSync(stdoutLogPath)) {
7450
7693
  artifactItems.push("- `stdout.log` \u2014 electron-app stdout output");
7451
7694
  }
7452
- if (fs6.existsSync(stderrLogPath)) {
7695
+ if (fs4.existsSync(stderrLogPath)) {
7453
7696
  artifactItems.push("- `stderr.log` \u2014 electron-app stderr output");
7454
7697
  }
7455
7698
  if (artifactItems.length > 0) {
@@ -7798,6 +8041,7 @@ var allLocalQaTools = [
7798
8041
  // Run result tools
7799
8042
  runResultListTool,
7800
8043
  runResultGetTool,
8044
+ runStepsGetTool,
7801
8045
  // Test script tools (read-only)
7802
8046
  testScriptListTool,
7803
8047
  testScriptGetTool,
@@ -8032,6 +8276,7 @@ __export(local_exports2, {
8032
8276
  RunResultGetInputSchema: () => RunResultGetInputSchema,
8033
8277
  RunResultListInputSchema: () => RunResultListInputSchema,
8034
8278
  RunResultStorageService: () => RunResultStorageService,
8279
+ RunStepsGetInputSchema: () => RunStepsGetInputSchema,
8035
8280
  SessionStatus: () => SessionStatus,
8036
8281
  SkillTelemetryEmitInputSchema: () => SkillTelemetryEmitInputSchema,
8037
8282
  StorageService: () => StorageService,
@@ -8355,4 +8600,4 @@ function applyOnboardingAnswers(answers, dataDirOverride2, now = /* @__PURE__ */
8355
8600
  return { isCompleted: true, preferences: prefs };
8356
8601
  }
8357
8602
 
8358
- export { DEFAULT_PREFERENCES, ElectronAppReleaseStream, EventName, ONBOARDING_BLANKET_OPTIONS, ONBOARDING_CHOICE_GROUPS, ONBOARDING_MAX_OFFERS, ONBOARDING_PRIMER_BULLETS, ONBOARDING_PRIMER_HEADLINE, ONBOARDING_TOGGLE_GROUPS, ONBOARDING_TOGGLE_PROMPT, ONBOARDING_UNSELECTED_TOGGLE_VALUE, OnboardingBlanketChoice, OnboardingGroupKind, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, ProjectPreferencesReconcileOutcome, RuntimeTarget, ServiceName, Surface, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, __export, __require, applyOnboardingAnswers, assertDeviceCodeClientProvisioned, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, buildOnboardingPlan, calculateFileChecksum, completeOnboarding, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports2 as e2e_exports, formatPreferencesOneLiner, getActiveElectronAppReleaseStream, getActiveRuntimeTarget, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir2 as getDataDir, getDisclosureCopy, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppReleaseTagPrefix, getElectronAppSignedFromVersion, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getOnboardingToggleKeys, getPlatformKey, getQaTools, getReleaseSignerIdentityUri, getValidApiKeyData, getValidCredentials, hasApiKey, hasShownDisclosure, initTelemetry, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports2 as local_exports, markDisclosureShown, markOnboardingCompleted, mcp_exports, needsOnboarding, openBrowserUrl, performLogin, performLogout, pollDeviceCode, readPreferencesMetadata, reconcileProjectPreferences, recordOnboardingSkip, resetConfig, resetLogger, resetPreference, resolveActiveProfile, resolveActiveReleaseStream, resolveActiveReleaseTagPrefix, resolveBlanketPreferences, resolveElectronAppPathOrNull, resolveOnboardingAnswers, resolvePreferences, resolveRuntimeTarget, resolveTogglePreferences, saveApiKey, saveApiKeyData, saveCredentials, src_exports, startDeviceCodeFlow, toolRequiresAuth, track, validatePreference, verifyFileChecksum, writePreferences2 as writePreferences, writePreferencesWithMetadata };
8603
+ export { ACTION_SCRIPT_FILE_NAME, DEFAULT_PREFERENCES, ElectronAppReleaseStream, EventName, HTML_VIEW_OFFER_LINE, ONBOARDING_BLANKET_OPTIONS, ONBOARDING_CHOICE_GROUPS, ONBOARDING_MAX_OFFERS, ONBOARDING_PRIMER_BULLETS, ONBOARDING_PRIMER_HEADLINE, ONBOARDING_TOGGLE_GROUPS, ONBOARDING_TOGGLE_PROMPT, ONBOARDING_UNSELECTED_TOGGLE_VALUE, OnboardingBlanketChoice, OnboardingGroupKind, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, ProjectPreferencesReconcileOutcome, RUN_SESSIONS_DIR_NAME, RunViewError, RuntimeTarget, SCREENSHOT_DIR_RELATIVE_PATH, SCREENSHOT_FILE_EXTENSION, ServiceName, Surface, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, __export, __require, applyOnboardingAnswers, assertDeviceCodeClientProvisioned, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, buildOnboardingPlan, calculateFileChecksum, completeOnboarding, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports2 as e2e_exports, formatPreferencesOneLiner, getActiveElectronAppReleaseStream, getActiveRuntimeTarget, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir2 as getDataDir, getDisclosureCopy, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppReleaseTagPrefix, getElectronAppSignedFromVersion, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getOnboardingToggleKeys, getPlatformKey, getQaTools, getReleaseSignerIdentityUri, getRunSessionsDir, getValidApiKeyData, getValidCredentials, hasApiKey, hasShownDisclosure, initTelemetry, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports2 as local_exports, markDisclosureShown, markOnboardingCompleted, mcp_exports, needsOnboarding, openBrowserUrl, performLogin, performLogout, pollDeviceCode, readPreferencesMetadata, readRunView, reconcileProjectPreferences, recordOnboardingSkip, renderRunViewHtml, renderRunViewText, resetConfig, resetLogger, resetPreference, resolveActiveProfile, resolveActiveReleaseStream, resolveActiveReleaseTagPrefix, resolveBlanketPreferences, resolveElectronAppPathOrNull, resolveOnboardingAnswers, resolvePreferences, resolveRunSessionPath, resolveRuntimeTarget, resolveTogglePreferences, saveApiKey, saveApiKeyData, saveCredentials, src_exports, startDeviceCodeFlow, toolRequiresAuth, track, validatePreference, verifyFileChecksum, writePreferences2 as writePreferences, writePreferencesWithMetadata, writeRunViewHtml };