mobbdev 1.2.36 → 1.2.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2696,12 +2696,11 @@ var init_env = __esm({
2696
2696
  });
2697
2697
 
2698
2698
  // src/mcp/core/configs.ts
2699
- var MCP_API_KEY_HEADER_NAME, MCP_LOGIN_MAX_WAIT, MCP_LOGIN_CHECK_DELAY, MCP_VUL_REPORT_DIGEST_TIMEOUT_MS, MCP_MAX_FILE_SIZE, MCP_PERIODIC_CHECK_INTERVAL, MCP_DEFAULT_MAX_FILES_TO_SCAN, MCP_REPORT_ID_EXPIRATION_MS, MCP_TOOLS_BROWSER_COOLDOWN_MS, MCP_DEFAULT_LIMIT, isAutoScan, MVS_AUTO_FIX_OVERRIDE, MCP_AUTO_FIX_DEBUG_MODE, MCP_PERIODIC_TRACK_INTERVAL, MCP_DEFAULT_REST_API_URL, MCP_SYSTEM_FIND_TIMEOUT_MS;
2699
+ var MCP_LOGIN_MAX_WAIT, MCP_LOGIN_CHECK_DELAY, MCP_VUL_REPORT_DIGEST_TIMEOUT_MS, MCP_MAX_FILE_SIZE, MCP_PERIODIC_CHECK_INTERVAL, MCP_DEFAULT_MAX_FILES_TO_SCAN, MCP_REPORT_ID_EXPIRATION_MS, MCP_TOOLS_BROWSER_COOLDOWN_MS, MCP_DEFAULT_LIMIT, isAutoScan, MVS_AUTO_FIX_OVERRIDE, MCP_AUTO_FIX_DEBUG_MODE, MCP_PERIODIC_TRACK_INTERVAL, MCP_DEFAULT_REST_API_URL, MCP_SYSTEM_FIND_TIMEOUT_MS;
2700
2700
  var init_configs = __esm({
2701
2701
  "src/mcp/core/configs.ts"() {
2702
2702
  "use strict";
2703
2703
  init_env();
2704
- MCP_API_KEY_HEADER_NAME = "x-mobb-key";
2705
2704
  MCP_LOGIN_MAX_WAIT = 2 * 60 * 1e3;
2706
2705
  MCP_LOGIN_CHECK_DELAY = 2 * 1e3;
2707
2706
  MCP_VUL_REPORT_DIGEST_TIMEOUT_MS = 30 * 60 * 1e3;
@@ -11013,16 +11012,20 @@ function createGitWithLogging(dirName, logger2 = defaultLogger) {
11013
11012
  }).outputHandler((bin, stdout2, stderr2) => {
11014
11013
  const callID = Math.random();
11015
11014
  logger2.info({ callID, bin }, "Start git CLI call");
11016
- const errChunks = [];
11017
- const outChunks = [];
11018
- let isStdoutClosed = false;
11019
- let isStderrClosed = false;
11020
- stderr2.on("data", (data) => errChunks.push(data.toString("utf8")));
11021
- stdout2.on("data", (data) => outChunks.push(data.toString("utf8")));
11022
- function logData() {
11023
- if (!isStderrClosed || !isStdoutClosed) {
11015
+ let errChunks = [];
11016
+ let outChunks = [];
11017
+ let isStdoutDone = false;
11018
+ let isStderrDone = false;
11019
+ const onStderrData = (data) => errChunks.push(data.toString("utf8"));
11020
+ const onStdoutData = (data) => outChunks.push(data.toString("utf8"));
11021
+ stderr2.on("data", onStderrData);
11022
+ stdout2.on("data", onStdoutData);
11023
+ let finalized = false;
11024
+ function finalizeAndCleanup() {
11025
+ if (finalized || !isStderrDone || !isStdoutDone) {
11024
11026
  return;
11025
11027
  }
11028
+ finalized = true;
11026
11029
  const logObj = {
11027
11030
  callID,
11028
11031
  bin,
@@ -11030,14 +11033,25 @@ function createGitWithLogging(dirName, logger2 = defaultLogger) {
11030
11033
  out: `${outChunks.join("").slice(0, 200)}...`
11031
11034
  };
11032
11035
  logger2.info(logObj, "git log output");
11033
- }
11034
- stderr2.on("close", () => {
11035
- isStderrClosed = true;
11036
- logData();
11036
+ stderr2.removeListener("data", onStderrData);
11037
+ stdout2.removeListener("data", onStdoutData);
11038
+ errChunks = [];
11039
+ outChunks = [];
11040
+ }
11041
+ function markDone(stream) {
11042
+ if (stream === "stderr") isStderrDone = true;
11043
+ else isStdoutDone = true;
11044
+ finalizeAndCleanup();
11045
+ }
11046
+ stderr2.on("close", () => markDone("stderr"));
11047
+ stdout2.on("close", () => markDone("stdout"));
11048
+ stderr2.on("error", (error) => {
11049
+ logger2.info({ callID, error: String(error) }, "git stderr stream error");
11050
+ markDone("stderr");
11037
11051
  });
11038
- stdout2.on("close", () => {
11039
- isStdoutClosed = true;
11040
- logData();
11052
+ stdout2.on("error", (error) => {
11053
+ logger2.info({ callID, error: String(error) }, "git stdout stream error");
11054
+ markDone("stdout");
11041
11055
  });
11042
11056
  });
11043
11057
  }
@@ -11749,14 +11763,10 @@ import open3 from "open";
11749
11763
  import tmp2 from "tmp";
11750
11764
  import { z as z31 } from "zod";
11751
11765
 
11752
- // src/commands/handleMobbLogin.ts
11753
- import chalk4 from "chalk";
11754
- import Debug10 from "debug";
11755
-
11756
11766
  // src/commands/AuthManager.ts
11757
11767
  import crypto from "crypto";
11758
11768
  import os3 from "os";
11759
- import Debug9 from "debug";
11769
+ import Debug10 from "debug";
11760
11770
  import open from "open";
11761
11771
 
11762
11772
  // src/features/analysis/graphql/gql.ts
@@ -11771,12 +11781,6 @@ var ApiConnectionError = class extends Error {
11771
11781
  this.name = "ApiConnectionError";
11772
11782
  }
11773
11783
  };
11774
- var CliLoginError = class extends Error {
11775
- constructor(message = "CLI login failed") {
11776
- super(message);
11777
- this.name = "CliLoginError";
11778
- }
11779
- };
11780
11784
  var AuthenticationError = class extends Error {
11781
11785
  constructor(message = "Authentication failed") {
11782
11786
  super(message);
@@ -12106,9 +12110,19 @@ function isAuthError(error) {
12106
12110
  }
12107
12111
  return false;
12108
12112
  }
12109
- function isNetworkError(error) {
12113
+ function isTransientError(error) {
12110
12114
  const errorString = error?.toString() ?? "";
12111
- return errorString.includes("FetchError") || errorString.includes("TypeError") || errorString.includes("ECONNREFUSED") || errorString.includes("ENOTFOUND") || errorString.includes("ETIMEDOUT") || errorString.includes("UND_ERR");
12115
+ if (errorString.includes("FetchError") || errorString.includes("TypeError") || errorString.includes("ECONNREFUSED") || errorString.includes("ENOTFOUND") || errorString.includes("ETIMEDOUT") || errorString.includes("UND_ERR")) {
12116
+ return true;
12117
+ }
12118
+ if (error instanceof ClientError) {
12119
+ const status = error.response?.status;
12120
+ if (status && status >= 502 && status <= 504) return true;
12121
+ }
12122
+ if (errorString.includes("Gateway Time-out") || errorString.includes("Bad Gateway") || errorString.includes("Service Unavailable")) {
12123
+ return true;
12124
+ }
12125
+ return false;
12112
12126
  }
12113
12127
  var API_KEY_HEADER_NAME = "x-mobb-key";
12114
12128
  var REPORT_STATE_CHECK_DELAY = 5 * 1e3;
@@ -12169,8 +12183,8 @@ var GQLClient = class {
12169
12183
  try {
12170
12184
  await this.getUserInfo();
12171
12185
  } catch (e) {
12172
- if (isNetworkError(e)) {
12173
- debug7("verify connection failed (network error) %o", e);
12186
+ if (isTransientError(e)) {
12187
+ debug7("verify connection failed (transient error) %o", e);
12174
12188
  return false;
12175
12189
  }
12176
12190
  debug7("verify connection: endpoint reachable but request failed %o", e);
@@ -12191,11 +12205,7 @@ var GQLClient = class {
12191
12205
  debug7("verify token failed - auth error %o", e);
12192
12206
  return false;
12193
12207
  }
12194
- if (isNetworkError(e)) {
12195
- debug7("verify token failed - network error, rethrowing %o", e);
12196
- throw e;
12197
- }
12198
- debug7("verify token failed - unexpected error, rethrowing %o", e);
12208
+ debug7("verify token failed - transient/unknown error, rethrowing %o", e);
12199
12209
  throw e;
12200
12210
  }
12201
12211
  }
@@ -12252,11 +12262,7 @@ var GQLClient = class {
12252
12262
  return res?.cli_login_by_pk?.encryptedApiToken || null;
12253
12263
  }
12254
12264
  async createCommunityUser() {
12255
- try {
12256
- await this._clientSdk.CreateCommunityUser();
12257
- } catch (e) {
12258
- debug7("create community user failed %o", e);
12259
- }
12265
+ await this._clientSdk.CreateCommunityUser();
12260
12266
  }
12261
12267
  async updateScmToken(args) {
12262
12268
  const { scmType, url, token, org, refreshToken } = args;
@@ -12579,23 +12585,127 @@ var GQLClient = class {
12579
12585
  // src/features/analysis/graphql/tracy-batch-upload.ts
12580
12586
  import { promisify } from "util";
12581
12587
  import { gzip } from "zlib";
12582
- import Debug8 from "debug";
12588
+ import Debug9 from "debug";
12583
12589
 
12584
12590
  // src/args/commands/upload_ai_blame.ts
12585
12591
  import fsPromises2 from "fs/promises";
12586
12592
  import * as os2 from "os";
12587
12593
  import path7 from "path";
12588
- import chalk3 from "chalk";
12594
+ import chalk4 from "chalk";
12589
12595
  import { withFile } from "tmp-promise";
12590
12596
  import z27 from "zod";
12597
+
12598
+ // src/commands/handleMobbLogin.ts
12599
+ import chalk3 from "chalk";
12600
+ import Debug7 from "debug";
12601
+ var debug8 = Debug7("mobbdev:commands");
12602
+ var MOBB_LOGIN_REQUIRED_MSG = `\u{1F513} Login to Mobb is Required, you will be redirected to our login page, once the authorization is complete return to this prompt, ${chalk3.bgBlue(
12603
+ "press any key to continue"
12604
+ )};`;
12605
+ async function getAuthenticatedGQLClient({
12606
+ inputApiKey = "",
12607
+ isSkipPrompts = true,
12608
+ apiUrl,
12609
+ webAppUrl
12610
+ }) {
12611
+ debug8(
12612
+ "getAuthenticatedGQLClient called with: apiUrl=%s, webAppUrl=%s",
12613
+ apiUrl || "undefined",
12614
+ webAppUrl || "undefined"
12615
+ );
12616
+ const authManager = new AuthManager(webAppUrl, apiUrl);
12617
+ let gqlClient = authManager.getGQLClient(inputApiKey);
12618
+ gqlClient = await handleMobbLogin({
12619
+ inGqlClient: gqlClient,
12620
+ skipPrompts: isSkipPrompts,
12621
+ apiUrl,
12622
+ webAppUrl
12623
+ });
12624
+ return gqlClient;
12625
+ }
12626
+ async function handleMobbLogin({
12627
+ inGqlClient,
12628
+ apiKey,
12629
+ skipPrompts,
12630
+ apiUrl,
12631
+ webAppUrl,
12632
+ loginContext,
12633
+ loginPath
12634
+ }) {
12635
+ debug8(
12636
+ "handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
12637
+ apiUrl || "fallback",
12638
+ apiUrl || "fallback",
12639
+ webAppUrl || "fallback",
12640
+ webAppUrl || "fallback"
12641
+ );
12642
+ const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
12643
+ const authManager = new AuthManager(webAppUrl, apiUrl);
12644
+ authManager.setGQLClient(inGqlClient);
12645
+ const authResult = await authManager.checkAuthentication();
12646
+ if (authResult.isAuthenticated) {
12647
+ createSpinner5().start().success({
12648
+ text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
12649
+ });
12650
+ return authManager.getGQLClient();
12651
+ }
12652
+ if (authResult.reason === "unknown") {
12653
+ debug8("Auth check returned unknown: %s", authResult.message);
12654
+ throw new CliError(`Cannot verify authentication: ${authResult.message}`);
12655
+ }
12656
+ if (apiKey) {
12657
+ createSpinner5().start().error({
12658
+ text: "\u{1F513} Login to Mobb failed: The provided API key does not match any configured API key on the system"
12659
+ });
12660
+ throw new CliError(
12661
+ "Login to Mobb failed: The provided API key does not match any configured API key on the system"
12662
+ );
12663
+ }
12664
+ const loginSpinner = createSpinner5().start();
12665
+ if (!skipPrompts) {
12666
+ loginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
12667
+ await keypress();
12668
+ }
12669
+ loginSpinner.update({
12670
+ text: "\u{1F513} Waiting for Mobb login..."
12671
+ });
12672
+ try {
12673
+ const loginUrl = await authManager.generateLoginUrl(loginPath, loginContext);
12674
+ if (!loginUrl) {
12675
+ loginSpinner.error({
12676
+ text: "Failed to generate login URL"
12677
+ });
12678
+ throw new CliError("Failed to generate login URL");
12679
+ }
12680
+ !skipPrompts && console.log(
12681
+ `If the page does not open automatically, kindly access it through ${loginUrl}.`
12682
+ );
12683
+ authManager.openUrlInBrowser();
12684
+ const authSuccess = await authManager.waitForAuthentication();
12685
+ if (!authSuccess) {
12686
+ loginSpinner.error({
12687
+ text: "Login timeout error"
12688
+ });
12689
+ throw new CliError("Login timeout error");
12690
+ }
12691
+ loginSpinner.success({
12692
+ text: `\u{1F513} Login to Mobb successful!`
12693
+ });
12694
+ return authManager.getGQLClient();
12695
+ } finally {
12696
+ authManager.cleanup();
12697
+ }
12698
+ }
12699
+
12700
+ // src/args/commands/upload_ai_blame.ts
12591
12701
  init_client_generates();
12592
12702
  init_GitService();
12593
12703
  init_urlParser2();
12594
12704
 
12595
12705
  // src/features/analysis/upload-file.ts
12596
- import Debug7 from "debug";
12706
+ import Debug8 from "debug";
12597
12707
  import fetch3, { File, fileFrom, FormData } from "node-fetch";
12598
- var debug8 = Debug7("mobbdev:upload-file");
12708
+ var debug9 = Debug8("mobbdev:upload-file");
12599
12709
  async function uploadFile({
12600
12710
  file,
12601
12711
  url,
@@ -12608,9 +12718,9 @@ async function uploadFile({
12608
12718
  logInfo2(`FileUpload: upload file start ${url}`);
12609
12719
  logInfo2(`FileUpload: upload fields`, uploadFields);
12610
12720
  logInfo2(`FileUpload: upload key ${uploadKey}`);
12611
- debug8("upload file start %s", url);
12612
- debug8("upload fields %o", uploadFields);
12613
- debug8("upload key %s", uploadKey);
12721
+ debug9("upload file start %s", url);
12722
+ debug9("upload fields %o", uploadFields);
12723
+ debug9("upload key %s", uploadKey);
12614
12724
  const form = new FormData();
12615
12725
  Object.entries(uploadFields).forEach(([key, value]) => {
12616
12726
  form.append(key, value);
@@ -12619,11 +12729,11 @@ async function uploadFile({
12619
12729
  form.append("key", uploadKey);
12620
12730
  }
12621
12731
  if (typeof file === "string") {
12622
- debug8("upload file from path %s", file);
12732
+ debug9("upload file from path %s", file);
12623
12733
  logInfo2(`FileUpload: upload file from path ${file}`);
12624
12734
  form.append("file", await fileFrom(file));
12625
12735
  } else {
12626
- debug8("upload file from buffer");
12736
+ debug9("upload file from buffer");
12627
12737
  logInfo2(`FileUpload: upload file from buffer`);
12628
12738
  form.append("file", new File([new Uint8Array(file)], "file"));
12629
12739
  }
@@ -12634,11 +12744,11 @@ async function uploadFile({
12634
12744
  agent
12635
12745
  });
12636
12746
  if (!response.ok) {
12637
- debug8("error from S3 %s %s", response.body, response.status);
12747
+ debug9("error from S3 %s %s", response.body, response.status);
12638
12748
  logInfo2(`FileUpload: error from S3 ${response.body} ${response.status}`);
12639
12749
  throw new Error(`Failed to upload the file: ${response.status}`);
12640
12750
  }
12641
- debug8("upload file done");
12751
+ debug9("upload file done");
12642
12752
  logInfo2(`FileUpload: upload file done`);
12643
12753
  }
12644
12754
 
@@ -12978,33 +13088,33 @@ function uploadAiBlameBuilder(args) {
12978
13088
  type: "string",
12979
13089
  array: true,
12980
13090
  demandOption: true,
12981
- describe: chalk3.bold("Path(s) to prompt artifact(s) (one per session)")
13091
+ describe: chalk4.bold("Path(s) to prompt artifact(s) (one per session)")
12982
13092
  }).option("inference", {
12983
13093
  type: "string",
12984
13094
  array: true,
12985
13095
  demandOption: true,
12986
- describe: chalk3.bold(
13096
+ describe: chalk4.bold(
12987
13097
  "Path(s) to inference artifact(s) (one per session)"
12988
13098
  )
12989
13099
  }).option("ai-response-at", {
12990
13100
  type: "string",
12991
13101
  array: true,
12992
- describe: chalk3.bold(
13102
+ describe: chalk4.bold(
12993
13103
  "ISO timestamp(s) for AI response (one per session, defaults to now)"
12994
13104
  )
12995
13105
  }).option("model", {
12996
13106
  type: "string",
12997
13107
  array: true,
12998
- describe: chalk3.bold("AI model name(s) (optional, one per session)")
13108
+ describe: chalk4.bold("AI model name(s) (optional, one per session)")
12999
13109
  }).option("tool-name", {
13000
13110
  type: "string",
13001
13111
  array: true,
13002
- describe: chalk3.bold("Tool/IDE name(s) (optional, one per session)")
13112
+ describe: chalk4.bold("Tool/IDE name(s) (optional, one per session)")
13003
13113
  }).option("blame-type", {
13004
13114
  type: "string",
13005
13115
  array: true,
13006
13116
  choices: Object.values(AiBlameInferenceType),
13007
- describe: chalk3.bold(
13117
+ describe: chalk4.bold(
13008
13118
  "Blame type(s) (optional, one per session, defaults to CHAT)"
13009
13119
  )
13010
13120
  }).strict();
@@ -13086,7 +13196,7 @@ async function uploadAiBlameHandler(options) {
13086
13196
  const sessionIds = args.sessionId || args["session-id"] || [];
13087
13197
  if (prompts.length !== inferences.length) {
13088
13198
  const errorMsg = "prompt and inference must have the same number of entries";
13089
- logger2.error(chalk3.red(errorMsg));
13199
+ logger2.error(chalk4.red(errorMsg));
13090
13200
  if (exitOnError) {
13091
13201
  process.exit(1);
13092
13202
  }
@@ -13106,7 +13216,7 @@ async function uploadAiBlameHandler(options) {
13106
13216
  ]);
13107
13217
  } catch {
13108
13218
  const errorMsg = `File not found for session ${i + 1}`;
13109
- logger2.error(chalk3.red(errorMsg));
13219
+ logger2.error(chalk4.red(errorMsg));
13110
13220
  if (exitOnError) {
13111
13221
  process.exit(1);
13112
13222
  }
@@ -13139,7 +13249,7 @@ async function uploadAiBlameHandler(options) {
13139
13249
  const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
13140
13250
  if (uploadSessions.length !== sessions.length) {
13141
13251
  const errorMsg = "Init failed to return expected number of sessions";
13142
- logger2.error(chalk3.red(errorMsg));
13252
+ logger2.error(chalk4.red(errorMsg));
13143
13253
  if (exitOnError) {
13144
13254
  process.exit(1);
13145
13255
  }
@@ -13196,7 +13306,7 @@ async function uploadAiBlameHandler(options) {
13196
13306
  if (status !== "OK") {
13197
13307
  const errorMsg = finRes?.finalizeAIBlameInferencesUpload?.error || "unknown error";
13198
13308
  logger2.error(
13199
- chalk3.red(
13309
+ chalk4.red(
13200
13310
  `[UPLOAD] Finalize failed with status: ${status}, error: ${errorMsg}`
13201
13311
  )
13202
13312
  );
@@ -13205,7 +13315,7 @@ async function uploadAiBlameHandler(options) {
13205
13315
  }
13206
13316
  throw new Error(errorMsg);
13207
13317
  }
13208
- logger2.info(chalk3.green("[UPLOAD] AI Blame uploads finalized successfully"));
13318
+ logger2.info(chalk4.green("[UPLOAD] AI Blame uploads finalized successfully"));
13209
13319
  } catch (error) {
13210
13320
  logger2.error("[UPLOAD] Finalize threw error:", error);
13211
13321
  throw error;
@@ -13217,7 +13327,7 @@ async function uploadAiBlameCommandHandler(args) {
13217
13327
 
13218
13328
  // src/features/analysis/graphql/tracy-batch-upload.ts
13219
13329
  var gzipAsync = promisify(gzip);
13220
- var debug9 = Debug8("mobbdev:tracy-batch-upload");
13330
+ var debug10 = Debug9("mobbdev:tracy-batch-upload");
13221
13331
  var MAX_BATCH_PAYLOAD_BYTES = 3 * 1024 * 1024;
13222
13332
 
13223
13333
  // src/mcp/services/types.ts
@@ -13253,9 +13363,9 @@ function buildLoginUrl(baseUrl, loginId, hostname, context) {
13253
13363
  }
13254
13364
 
13255
13365
  // src/commands/AuthManager.ts
13256
- var debug10 = Debug9("mobbdev:auth");
13257
- var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
13258
- var LOGIN_CHECK_DELAY = 5 * 1e3;
13366
+ var debug11 = Debug10("mobbdev:auth");
13367
+ var LOGIN_MAX_WAIT = 2 * 60 * 1e3;
13368
+ var LOGIN_CHECK_DELAY = 2 * 1e3;
13259
13369
  var _AuthManager = class _AuthManager {
13260
13370
  constructor(webAppUrl, apiUrl) {
13261
13371
  __publicField(this, "publicKey");
@@ -13269,22 +13379,33 @@ var _AuthManager = class _AuthManager {
13269
13379
  this.resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
13270
13380
  this.resolvedApiUrl = apiUrl || API_URL;
13271
13381
  }
13382
+ /** Set the minimum interval between browser opens (MCP sets this to 24h) */
13383
+ static setBrowserCooldown(ms) {
13384
+ _AuthManager.browserCooldownMs = ms;
13385
+ }
13386
+ /** Reset cooldown state. Used by tests to ensure isolation. */
13387
+ static resetCooldown() {
13388
+ _AuthManager.lastBrowserOpenTime = 0;
13389
+ _AuthManager.browserCooldownMs = 0;
13390
+ }
13272
13391
  openUrlInBrowser() {
13273
- if (this.currentBrowserUrl) {
13274
- open(this.currentBrowserUrl);
13275
- return true;
13392
+ if (!this.currentBrowserUrl) {
13393
+ return false;
13276
13394
  }
13277
- return false;
13395
+ if (_AuthManager.browserCooldownMs > 0 && Date.now() - _AuthManager.lastBrowserOpenTime < _AuthManager.browserCooldownMs) {
13396
+ debug11("browser cooldown active, skipping open");
13397
+ return false;
13398
+ }
13399
+ open(this.currentBrowserUrl);
13400
+ _AuthManager.lastBrowserOpenTime = Date.now();
13401
+ return true;
13278
13402
  }
13403
+ /**
13404
+ * Polls for the encrypted API token, decrypts it, validates it, and stores it.
13405
+ * Returns true if login succeeded.
13406
+ */
13279
13407
  async waitForAuthentication() {
13280
- let newApiToken = null;
13281
- for (let i = 0; i < _AuthManager.loginMaxWait / LOGIN_CHECK_DELAY; i++) {
13282
- newApiToken = await this.getApiToken();
13283
- if (newApiToken) {
13284
- break;
13285
- }
13286
- await sleep(LOGIN_CHECK_DELAY);
13287
- }
13408
+ const newApiToken = await this.waitForApiToken();
13288
13409
  if (!newApiToken) {
13289
13410
  return false;
13290
13411
  }
@@ -13302,52 +13423,75 @@ var _AuthManager = class _AuthManager {
13302
13423
  return false;
13303
13424
  }
13304
13425
  /**
13305
- * Checks if the user is already authenticated
13426
+ * Polls for the encrypted API token and decrypts it.
13427
+ * Does NOT validate or store the token — use this when the caller
13428
+ * needs to validate on a specific client type (e.g. McpGQLClient).
13429
+ */
13430
+ async waitForApiToken() {
13431
+ for (let i = 0; i < _AuthManager.loginMaxWait / LOGIN_CHECK_DELAY; i++) {
13432
+ const token = await this.getApiToken();
13433
+ if (token) {
13434
+ return token;
13435
+ }
13436
+ await sleep(LOGIN_CHECK_DELAY);
13437
+ }
13438
+ return null;
13439
+ }
13440
+ /**
13441
+ * Checks if the user is already authenticated.
13442
+ * Returns the full AuthResult so callers can distinguish 'invalid' from 'unknown'.
13306
13443
  */
13307
13444
  async isAuthenticated() {
13308
13445
  if (this.authenticated === null) {
13309
13446
  const result = await this.checkAuthentication();
13310
13447
  this.authenticated = result.isAuthenticated;
13311
13448
  if (!result.isAuthenticated) {
13312
- debug10("isAuthenticated: false \u2014 %s", result.message);
13449
+ debug11("isAuthenticated: false \u2014 %s (%s)", result.message, result.reason);
13313
13450
  }
13314
13451
  }
13315
13452
  return this.authenticated;
13316
13453
  }
13317
13454
  /**
13318
- * Private function to check if the user is authenticated with the server
13455
+ * Full 3-state auth check returning an {@link AuthResult}.
13456
+ *
13457
+ * - `isAuthenticated: true` — token is valid.
13458
+ * - `reason: 'invalid'` — definitive auth failure (access-denied). Caller should trigger login.
13459
+ * - `reason: 'unknown'` — transient/network error. Caller should NOT trigger login.
13319
13460
  */
13320
13461
  async checkAuthentication(apiKey) {
13462
+ if (!this.gqlClient) {
13463
+ this.gqlClient = this.getGQLClient(apiKey);
13464
+ }
13465
+ const isConnected = await this.gqlClient.verifyApiConnection();
13466
+ if (!isConnected) {
13467
+ return {
13468
+ isAuthenticated: false,
13469
+ reason: "unknown",
13470
+ message: "Failed to connect to Mobb server"
13471
+ };
13472
+ }
13321
13473
  try {
13322
- if (!this.gqlClient) {
13323
- this.gqlClient = this.getGQLClient(apiKey);
13324
- }
13325
- const isConnected = await this.gqlClient.verifyApiConnection();
13326
- if (!isConnected) {
13327
- return {
13328
- isAuthenticated: false,
13329
- message: "Failed to connect to Mobb server"
13330
- };
13331
- }
13332
13474
  const userVerify = await this.gqlClient.validateUserToken();
13333
13475
  if (!userVerify) {
13334
13476
  return {
13335
13477
  isAuthenticated: false,
13478
+ reason: "invalid",
13336
13479
  message: "User token validation failed"
13337
13480
  };
13338
13481
  }
13339
13482
  } catch (error) {
13340
13483
  return {
13341
13484
  isAuthenticated: false,
13485
+ reason: "unknown",
13342
13486
  message: error instanceof Error ? error.message : "Unknown authentication error"
13343
13487
  };
13344
13488
  }
13345
- return { isAuthenticated: true, message: "Successfully authenticated" };
13489
+ return { isAuthenticated: true };
13346
13490
  }
13347
13491
  /**
13348
13492
  * Generates a login URL for manual authentication
13349
13493
  */
13350
- async generateLoginUrl(loginContext) {
13494
+ async generateLoginUrl(loginPath, loginContext) {
13351
13495
  try {
13352
13496
  if (!this.gqlClient) {
13353
13497
  this.gqlClient = this.getGQLClient();
@@ -13360,7 +13504,7 @@ var _AuthManager = class _AuthManager {
13360
13504
  this.loginId = await this.gqlClient.createCliLogin({
13361
13505
  publicKey: this.publicKey.export({ format: "pem", type: "pkcs1" }).toString()
13362
13506
  });
13363
- const webLoginUrl = `${this.resolvedWebAppUrl}/cli-login`;
13507
+ const webLoginUrl = `${this.resolvedWebAppUrl}${loginPath || "/cli-login"}`;
13364
13508
  const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, this.loginId, os3.hostname(), loginContext) : `${webLoginUrl}/${this.loginId}?hostname=${os3.hostname()}`;
13365
13509
  this.currentBrowserUrl = browserUrl;
13366
13510
  return browserUrl;
@@ -13370,22 +13514,32 @@ var _AuthManager = class _AuthManager {
13370
13514
  }
13371
13515
  }
13372
13516
  /**
13373
- * Retrieves and decrypts the API token after authentication
13517
+ * Retrieves and decrypts the API token after authentication.
13518
+ * Returns null if the token is not yet available or on transient errors.
13374
13519
  */
13375
13520
  async getApiToken() {
13376
13521
  if (!this.gqlClient || !this.loginId || !this.privateKey) {
13377
13522
  return null;
13378
13523
  }
13379
- const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
13380
- loginId: this.loginId
13381
- });
13382
- if (encryptedApiToken) {
13383
- return crypto.privateDecrypt(
13384
- this.privateKey,
13385
- Buffer.from(encryptedApiToken, "base64")
13386
- ).toString("utf-8");
13524
+ try {
13525
+ const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
13526
+ loginId: this.loginId
13527
+ });
13528
+ if (encryptedApiToken) {
13529
+ return crypto.privateDecrypt(
13530
+ this.privateKey,
13531
+ Buffer.from(encryptedApiToken, "base64")
13532
+ ).toString("utf-8");
13533
+ }
13534
+ return null;
13535
+ } catch (error) {
13536
+ if (isTransientError(error)) {
13537
+ debug11("getApiToken: transient error, will retry");
13538
+ } else {
13539
+ debug11("getApiToken: unexpected error: %O", error);
13540
+ }
13541
+ return null;
13387
13542
  }
13388
- return null;
13389
13543
  }
13390
13544
  /**
13391
13545
  * Returns true if a non-empty API token is stored in the configStore.
@@ -13429,109 +13583,11 @@ var _AuthManager = class _AuthManager {
13429
13583
  };
13430
13584
  /** Maximum time (ms) to wait for login authentication. Override in tests for faster failures. */
13431
13585
  __publicField(_AuthManager, "loginMaxWait", LOGIN_MAX_WAIT);
13586
+ /** Browser cooldown: minimum ms between browser opens (0 = no cooldown) */
13587
+ __publicField(_AuthManager, "browserCooldownMs", 0);
13588
+ __publicField(_AuthManager, "lastBrowserOpenTime", 0);
13432
13589
  var AuthManager = _AuthManager;
13433
13590
 
13434
- // src/commands/handleMobbLogin.ts
13435
- var debug11 = Debug10("mobbdev:commands");
13436
- var LOGIN_MAX_WAIT2 = 10 * 60 * 1e3;
13437
- var LOGIN_CHECK_DELAY2 = 5 * 1e3;
13438
- var MOBB_LOGIN_REQUIRED_MSG = `\u{1F513} Login to Mobb is Required, you will be redirected to our login page, once the authorization is complete return to this prompt, ${chalk4.bgBlue(
13439
- "press any key to continue"
13440
- )};`;
13441
- async function getAuthenticatedGQLClient({
13442
- inputApiKey = "",
13443
- isSkipPrompts = true,
13444
- apiUrl,
13445
- webAppUrl
13446
- }) {
13447
- debug11(
13448
- "getAuthenticatedGQLClient called with: apiUrl=%s, webAppUrl=%s",
13449
- apiUrl || "undefined",
13450
- webAppUrl || "undefined"
13451
- );
13452
- const authManager = new AuthManager(webAppUrl, apiUrl);
13453
- let gqlClient = authManager.getGQLClient(inputApiKey);
13454
- gqlClient = await handleMobbLogin({
13455
- inGqlClient: gqlClient,
13456
- skipPrompts: isSkipPrompts,
13457
- apiUrl,
13458
- webAppUrl
13459
- });
13460
- return gqlClient;
13461
- }
13462
- async function handleMobbLogin({
13463
- inGqlClient,
13464
- apiKey,
13465
- skipPrompts,
13466
- apiUrl,
13467
- webAppUrl,
13468
- loginContext
13469
- }) {
13470
- debug11(
13471
- "handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
13472
- apiUrl || "fallback",
13473
- apiUrl || "fallback",
13474
- webAppUrl || "fallback",
13475
- webAppUrl || "fallback"
13476
- );
13477
- const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
13478
- const authManager = new AuthManager(webAppUrl, apiUrl);
13479
- authManager.setGQLClient(inGqlClient);
13480
- try {
13481
- const isAuthenticated = await authManager.isAuthenticated();
13482
- if (isAuthenticated) {
13483
- createSpinner5().start().success({
13484
- text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
13485
- });
13486
- return authManager.getGQLClient();
13487
- }
13488
- } catch (error) {
13489
- debug11("Authentication check failed:", error);
13490
- }
13491
- if (apiKey) {
13492
- createSpinner5().start().error({
13493
- text: "\u{1F513} Login to Mobb failed: The provided API key does not match any configured API key on the system"
13494
- });
13495
- throw new CliError(
13496
- "Login to Mobb failed: The provided API key does not match any configured API key on the system"
13497
- );
13498
- }
13499
- const loginSpinner = createSpinner5().start();
13500
- if (!skipPrompts) {
13501
- loginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
13502
- await keypress();
13503
- }
13504
- loginSpinner.update({
13505
- text: "\u{1F513} Waiting for Mobb login..."
13506
- });
13507
- try {
13508
- const loginUrl = await authManager.generateLoginUrl(loginContext);
13509
- if (!loginUrl) {
13510
- loginSpinner.error({
13511
- text: "Failed to generate login URL"
13512
- });
13513
- throw new CliError("Failed to generate login URL");
13514
- }
13515
- !skipPrompts && console.log(
13516
- `If the page does not open automatically, kindly access it through ${loginUrl}.`
13517
- );
13518
- authManager.openUrlInBrowser();
13519
- const authSuccess = await authManager.waitForAuthentication();
13520
- if (!authSuccess) {
13521
- loginSpinner.error({
13522
- text: "Login timeout error"
13523
- });
13524
- throw new CliError("Login timeout error");
13525
- }
13526
- loginSpinner.success({
13527
- text: `\u{1F513} Login to Mobb successful!`
13528
- });
13529
- return authManager.getGQLClient();
13530
- } finally {
13531
- authManager.cleanup();
13532
- }
13533
- }
13534
-
13535
13591
  // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
13536
13592
  import Debug14 from "debug";
13537
13593
 
@@ -14490,8 +14546,8 @@ if (typeof __filename !== "undefined") {
14490
14546
  }
14491
14547
  var costumeRequire = createRequire(moduleUrl);
14492
14548
  var getCheckmarxPath = () => {
14493
- const os14 = type();
14494
- const cxFileName = os14 === "Windows_NT" ? "cx.exe" : "cx";
14549
+ const os13 = type();
14550
+ const cxFileName = os13 === "Windows_NT" ? "cx.exe" : "cx";
14495
14551
  try {
14496
14552
  return costumeRequire.resolve(`.bin/${cxFileName}`);
14497
14553
  } catch (e) {
@@ -15082,7 +15138,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
15082
15138
  `If the page does not open automatically, kindly access it through ${scmAuthUrl2}.`
15083
15139
  );
15084
15140
  await open3(scmAuthUrl2);
15085
- for (let i = 0; i < LOGIN_MAX_WAIT2 / LOGIN_CHECK_DELAY2; i++) {
15141
+ for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
15086
15142
  const userInfo2 = await gqlClient.getUserInfo();
15087
15143
  if (!userInfo2) {
15088
15144
  throw new CliError2("User info not found");
@@ -15101,7 +15157,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
15101
15157
  return tokenInfo2.accessToken;
15102
15158
  }
15103
15159
  scmSpinner.spin();
15104
- await sleep(LOGIN_CHECK_DELAY2);
15160
+ await sleep(LOGIN_CHECK_DELAY);
15105
15161
  }
15106
15162
  scmSpinner.error({
15107
15163
  text: `${scmName} login timeout error`
@@ -16429,9 +16485,7 @@ var logDebug = (message, data) => logger.log(message, "debug", data);
16429
16485
  var log = logger.log.bind(logger);
16430
16486
 
16431
16487
  // src/mcp/services/McpGQLClient.ts
16432
- import crypto3 from "crypto";
16433
- import { ClientError as ClientError2, GraphQLClient as GraphQLClient2 } from "graphql-request";
16434
- import { v4 as uuidv42 } from "uuid";
16488
+ import crypto2 from "crypto";
16435
16489
  init_client_generates();
16436
16490
  init_configs();
16437
16491
 
@@ -16564,197 +16618,37 @@ var GetLatestReportByRepoUrlResponseSchema = z34.object({
16564
16618
  expiredReport: z34.array(ExpiredReportSchema)
16565
16619
  });
16566
16620
 
16567
- // src/mcp/services/McpAuthService.ts
16568
- import crypto2 from "crypto";
16569
- import os5 from "os";
16570
- import open4 from "open";
16571
- init_configs();
16572
- var _McpAuthService = class _McpAuthService {
16573
- constructor(client) {
16574
- __publicField(this, "client");
16575
- this.client = client;
16576
- }
16577
- /** Reset cooldown state. Used by tests to ensure isolation. */
16578
- static resetCooldown() {
16579
- _McpAuthService.lastBrowserOpenTime = 0;
16580
- }
16581
- /**
16582
- * Opens a browser window for authentication
16583
- * @param url URL to open in browser
16584
- * @param isBackgoundCall Whether this is called from tools context
16585
- */
16586
- async openBrowser(url, isBackgoundCall) {
16587
- if (isBackgoundCall) {
16588
- const now = Date.now();
16589
- if (now - _McpAuthService.lastBrowserOpenTime < MCP_TOOLS_BROWSER_COOLDOWN_MS) {
16590
- logDebug(`browser cooldown active, skipping open for ${url}`);
16591
- return false;
16592
- }
16593
- }
16594
- logDebug(`opening browser url ${url}`);
16595
- await open4(url);
16596
- _McpAuthService.lastBrowserOpenTime = Date.now();
16597
- return true;
16598
- }
16599
- /**
16600
- * Handles the complete authentication flow
16601
- * @param isBackgoundCall Whether this is called from tools context
16602
- * @param loginContext Context information about who triggered the login
16603
- * @returns Authenticated API token
16604
- */
16605
- async authenticate(isBackgoundCall = false, loginContext) {
16606
- const { publicKey, privateKey } = crypto2.generateKeyPairSync("rsa", {
16607
- modulusLength: 2048
16608
- });
16609
- logDebug("creating cli login");
16610
- const loginId = await this.client.createCliLogin({
16611
- publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
16612
- });
16613
- if (!loginId) {
16614
- throw new CliLoginError("Error: createCliLogin failed");
16615
- }
16616
- logDebug(`cli login created ${loginId}`);
16617
- const webLoginUrl = `${WEB_APP_URL}/mvs-login`;
16618
- const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, loginId, os5.hostname(), loginContext) : `${webLoginUrl}/${loginId}?hostname=${os5.hostname()}`;
16619
- const browserOpened = await this.openBrowser(browserUrl, isBackgoundCall);
16620
- if (!browserOpened) {
16621
- throw new AuthenticationError(
16622
- "Authentication required but browser cooldown is active"
16623
- );
16624
- }
16625
- logDebug(`waiting for login to complete`);
16626
- let newApiToken = null;
16627
- for (let i = 0; i < MCP_LOGIN_MAX_WAIT / MCP_LOGIN_CHECK_DELAY; i++) {
16628
- const encryptedApiToken = await this.client.getEncryptedApiToken({
16629
- loginId
16630
- });
16631
- if (encryptedApiToken) {
16632
- logDebug("encrypted API token received");
16633
- newApiToken = crypto2.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
16634
- logDebug("API token decrypted");
16635
- break;
16636
- }
16637
- await sleep(MCP_LOGIN_CHECK_DELAY);
16638
- }
16639
- if (!newApiToken) {
16640
- throw new FailedToGetApiTokenError(
16641
- "Error: failed to get encrypted api token"
16642
- );
16643
- }
16644
- const verifyClient = new McpGQLClient({
16645
- apiKey: newApiToken,
16646
- type: "apiKey"
16647
- });
16648
- const loginSuccess = await verifyClient.validateUserToken();
16649
- if (!loginSuccess) {
16650
- throw new AuthenticationError("Invalid API token");
16651
- }
16652
- return newApiToken;
16653
- }
16654
- };
16655
- // Static so cooldown persists across McpAuthService instances
16656
- __publicField(_McpAuthService, "lastBrowserOpenTime", 0);
16657
- var McpAuthService = _McpAuthService;
16658
-
16659
16621
  // src/mcp/services/McpGQLClient.ts
16660
- function isAuthError2(error) {
16661
- if (error instanceof ClientError2) {
16662
- const gqlErrors = error.response?.errors;
16663
- return gqlErrors?.some(
16664
- (e) => e.extensions?.["code"] === "access-denied" || e.message?.includes("Authentication hook unauthorized")
16665
- ) ?? false;
16666
- }
16667
- return false;
16668
- }
16669
- function isNetworkError2(error) {
16670
- const errorString = error?.toString() ?? "";
16671
- return errorString.includes("FetchError") || errorString.includes("TypeError") || errorString.includes("ECONNREFUSED") || errorString.includes("ENOTFOUND") || errorString.includes("ETIMEDOUT") || errorString.includes("UND_ERR");
16672
- }
16673
- var McpGQLClient = class {
16622
+ var McpGQLClient = class extends GQLClient {
16674
16623
  constructor(args) {
16675
- __publicField(this, "client");
16676
- __publicField(this, "clientSdk");
16677
- __publicField(this, "_auth");
16678
- __publicField(this, "currentUser", null);
16679
- __publicField(this, "apiUrl");
16680
- this._auth = args;
16681
- this.apiUrl = process.env["API_URL"] || DEFAULT_API_URL;
16682
- logDebug(`[GraphQL] Creating graphql client with api url ${this.apiUrl}`, {
16683
- args
16684
- });
16685
- this.client = new GraphQLClient2(this.apiUrl, {
16686
- headers: args.type === "apiKey" ? { [MCP_API_KEY_HEADER_NAME]: args.apiKey || "" } : {
16687
- Authorization: `Bearer ${args.token}`
16688
- },
16689
- fetch: fetchWithProxy,
16690
- requestMiddleware: (request) => {
16691
- const requestId = uuidv42();
16692
- return {
16693
- ...request,
16694
- headers: {
16695
- ...request.headers,
16696
- "x-hasura-request-id": requestId
16697
- }
16698
- };
16699
- }
16624
+ super({
16625
+ ...args,
16626
+ apiUrl: process.env["API_URL"] || void 0
16700
16627
  });
16701
- this.clientSdk = getSdk(this.client);
16628
+ __publicField(this, "currentUser", null);
16702
16629
  }
16703
16630
  getErrorContext() {
16704
16631
  return {
16705
- endpoint: this.apiUrl,
16632
+ endpoint: this._apiUrl,
16706
16633
  apiKey: this._auth.type === "apiKey" ? this._auth.apiKey : "",
16707
16634
  headers: {
16708
- [MCP_API_KEY_HEADER_NAME]: this._auth.type === "apiKey" ? "[REDACTED]" : "undefined",
16635
+ "x-mobb-key": this._auth.type === "apiKey" ? "[REDACTED]" : "undefined",
16709
16636
  "x-hasura-request-id": "[DYNAMIC]"
16710
16637
  }
16711
16638
  };
16712
16639
  }
16713
- async uploadAIBlameInferencesInitRaw(variables) {
16714
- return await this.clientSdk.UploadAIBlameInferencesInit(variables);
16715
- }
16716
- async finalizeAIBlameInferencesUploadRaw(variables) {
16717
- return await this.clientSdk.FinalizeAIBlameInferencesUpload(variables);
16718
- }
16719
- async isApiEndpointReachable() {
16720
- try {
16721
- logDebug("[GraphQL] Calling Me query for API connection verification");
16722
- const result = await this.getUserInfo();
16723
- logDebug("[GraphQL] Me query successful", { result });
16724
- return true;
16725
- } catch (e) {
16726
- logDebug("[GraphQL] API connection verification failed", { error: e });
16727
- if (isNetworkError2(e)) {
16728
- logError("[GraphQL] API endpoint unreachable (network error)", {
16729
- error: e
16730
- });
16731
- return false;
16732
- }
16733
- logDebug("[GraphQL] API endpoint is reachable (non-network error)");
16734
- return true;
16735
- }
16640
+ async getUserInfo() {
16641
+ const me = await super.getUserInfo();
16642
+ this.currentUser = me;
16643
+ return me;
16736
16644
  }
16737
- /**
16738
- * Verifies both API endpoint reachability and user authentication
16739
- * @returns true if both API is reachable and user is authenticated
16740
- */
16741
- async verifyApiConnection() {
16742
- const isReachable = await this.isApiEndpointReachable();
16743
- if (!isReachable) {
16744
- return false;
16745
- }
16746
- try {
16747
- await this.validateUserToken();
16748
- return true;
16749
- } catch (e) {
16750
- logError("User token validation failed", { error: e });
16751
- return false;
16752
- }
16645
+ getCurrentUser() {
16646
+ return this.currentUser;
16753
16647
  }
16754
16648
  async uploadS3BucketInfo() {
16755
16649
  try {
16756
16650
  logDebug("[GraphQL] Calling uploadS3BucketInfo mutation");
16757
- const result = await this.clientSdk.uploadS3BucketInfo({
16651
+ const result = await this._clientSdk.uploadS3BucketInfo({
16758
16652
  fileName: "report.json"
16759
16653
  });
16760
16654
  logDebug("[GraphQL] uploadS3BucketInfo successful", { result });
@@ -16770,7 +16664,7 @@ var McpGQLClient = class {
16770
16664
  async getAnalysis(analysisId) {
16771
16665
  try {
16772
16666
  logDebug("[GraphQL] Calling getAnalysis query", { analysisId });
16773
- const res = await this.clientSdk.getAnalysis({
16667
+ const res = await this._clientSdk.getAnalysis({
16774
16668
  analysisId
16775
16669
  });
16776
16670
  logDebug("[GraphQL] getAnalysis successful", { result: res });
@@ -16792,7 +16686,7 @@ var McpGQLClient = class {
16792
16686
  logDebug("[GraphQL] Calling SubmitVulnerabilityReport mutation", {
16793
16687
  variables
16794
16688
  });
16795
- const result = await this.clientSdk.SubmitVulnerabilityReport(variables);
16689
+ const result = await this._clientSdk.SubmitVulnerabilityReport(variables);
16796
16690
  logDebug("[GraphQL] SubmitVulnerabilityReport successful", { result });
16797
16691
  return result;
16798
16692
  } catch (e) {
@@ -16871,15 +16765,15 @@ var McpGQLClient = class {
16871
16765
  this._auth.type === "apiKey" ? {
16872
16766
  apiKey: this._auth.apiKey,
16873
16767
  type: "apiKey",
16874
- url: httpToWsUrl(this.apiUrl),
16768
+ url: httpToWsUrl(this._apiUrl),
16875
16769
  timeoutInMs: params.timeoutInMs,
16876
- proxyAgent: getProxyAgent(this.apiUrl)
16770
+ proxyAgent: getProxyAgent(this._apiUrl)
16877
16771
  } : {
16878
16772
  token: this._auth.token,
16879
16773
  type: "token",
16880
- url: httpToWsUrl(this.apiUrl),
16774
+ url: httpToWsUrl(this._apiUrl),
16881
16775
  timeoutInMs: params.timeoutInMs,
16882
- proxyAgent: getProxyAgent(this.apiUrl)
16776
+ proxyAgent: getProxyAgent(this._apiUrl)
16883
16777
  }
16884
16778
  );
16885
16779
  }
@@ -16903,12 +16797,12 @@ var McpGQLClient = class {
16903
16797
  if (!userEmail) {
16904
16798
  throw new Error("User email not found");
16905
16799
  }
16906
- const shortEmailHash = crypto3.createHash("sha256").update(userEmail).digest("hex").slice(0, 8).toUpperCase();
16800
+ const shortEmailHash = crypto2.createHash("sha256").update(userEmail).digest("hex").slice(0, 8).toUpperCase();
16907
16801
  const projectName = `MCP Scans ${shortEmailHash}`;
16908
16802
  logDebug("[GraphQL] Calling getLastOrgAndNamedProject query", {
16909
16803
  projectName
16910
16804
  });
16911
- const orgAndProjectRes = await this.clientSdk.getLastOrgAndNamedProject({
16805
+ const orgAndProjectRes = await this._clientSdk.getLastOrgAndNamedProject({
16912
16806
  email: userEmail,
16913
16807
  projectName
16914
16808
  });
@@ -16934,7 +16828,7 @@ var McpGQLClient = class {
16934
16828
  projectName
16935
16829
  });
16936
16830
  try {
16937
- const createdProject = await this.clientSdk.CreateProject({
16831
+ const createdProject = await this._clientSdk.CreateProject({
16938
16832
  organizationId: organization.id,
16939
16833
  projectName
16940
16834
  });
@@ -16956,7 +16850,7 @@ var McpGQLClient = class {
16956
16850
  error: errorMessage
16957
16851
  }
16958
16852
  );
16959
- const retryOrgAndProjectRes = await this.clientSdk.getLastOrgAndNamedProject({
16853
+ const retryOrgAndProjectRes = await this._clientSdk.getLastOrgAndNamedProject({
16960
16854
  email: userEmail,
16961
16855
  projectName
16962
16856
  });
@@ -16990,71 +16884,6 @@ var McpGQLClient = class {
16990
16884
  throw e;
16991
16885
  }
16992
16886
  }
16993
- async getUserInfo() {
16994
- const { me } = await this.clientSdk.Me();
16995
- this.currentUser = me;
16996
- return me;
16997
- }
16998
- getCurrentUser() {
16999
- return this.currentUser;
17000
- }
17001
- async validateUserToken() {
17002
- logDebug("[GraphQL] Validating user token");
17003
- try {
17004
- await this.clientSdk.CreateCommunityUser();
17005
- const info = await this.getUserInfo();
17006
- if (!info) {
17007
- logDebug("[GraphQL] User token is invalid (no user info returned)");
17008
- return false;
17009
- }
17010
- logDebug("[GraphQL] User token validated successfully");
17011
- return info.email || true;
17012
- } catch (e) {
17013
- if (isAuthError2(e)) {
17014
- logDebug("[GraphQL] User token is invalid (auth error from server)");
17015
- return false;
17016
- }
17017
- if (isNetworkError2(e)) {
17018
- logError("[GraphQL] Token validation failed due to network error", {
17019
- error: e
17020
- });
17021
- throw e;
17022
- }
17023
- logError("[GraphQL] Token validation failed with unexpected error", {
17024
- error: e
17025
- });
17026
- throw e;
17027
- }
17028
- }
17029
- async createCliLogin(variables) {
17030
- try {
17031
- const res = await this.clientSdk.CreateCliLogin(variables, {
17032
- // We may have outdated API key in the config storage. Avoid using it for the login request.
17033
- [MCP_API_KEY_HEADER_NAME]: ""
17034
- });
17035
- const loginId = res.insert_cli_login_one?.id || "";
17036
- if (!loginId) {
17037
- logError("[GraphQL] Create cli login failed - no login ID returned");
17038
- return "";
17039
- }
17040
- return loginId;
17041
- } catch (e) {
17042
- logError("[GraphQL] Create cli login failed", { error: e });
17043
- return "";
17044
- }
17045
- }
17046
- async getEncryptedApiToken(variables) {
17047
- try {
17048
- const res = await this.clientSdk.GetEncryptedApiToken(variables, {
17049
- // We may have outdated API key in the config storage. Avoid using it for the login request.
17050
- [MCP_API_KEY_HEADER_NAME]: ""
17051
- });
17052
- return res?.cli_login_by_pk?.encryptedApiToken || null;
17053
- } catch (e) {
17054
- logError("[GraphQL] Get encrypted api token failed", { error: e });
17055
- return null;
17056
- }
17057
- }
17058
16887
  generateFixUrl({
17059
16888
  fixId,
17060
16889
  organizationId,
@@ -17064,7 +16893,7 @@ var McpGQLClient = class {
17064
16893
  if (!organizationId || !projectId || !reportId) {
17065
16894
  return void 0;
17066
16895
  }
17067
- const appBaseUrl = this.apiUrl.replace("/v1/graphql", "").replace("api.", "");
16896
+ const appBaseUrl = this._apiUrl.replace("/v1/graphql", "").replace("api.", "");
17068
16897
  return `${appBaseUrl}/organization/${organizationId}/project/${projectId}/report/${reportId}/fix/${fixId}`;
17069
16898
  }
17070
16899
  mergeUserAndSystemFixes({
@@ -17111,7 +16940,7 @@ var McpGQLClient = class {
17111
16940
  }
17112
16941
  async updateFixesDownloadStatus(fixIds) {
17113
16942
  if (fixIds.length > 0) {
17114
- const resUpdate = await this.clientSdk.updateDownloadedFixData({
16943
+ const resUpdate = await this._clientSdk.updateDownloadedFixData({
17115
16944
  fixIds,
17116
16945
  source: "MCP" /* Mcp */
17117
16946
  });
@@ -17125,7 +16954,7 @@ var McpGQLClient = class {
17125
16954
  }
17126
16955
  async updateAutoAppliedFixesStatus(fixIds) {
17127
16956
  if (fixIds.length > 0) {
17128
- const resUpdate = await this.clientSdk.updateDownloadedFixData({
16957
+ const resUpdate = await this._clientSdk.updateDownloadedFixData({
17129
16958
  fixIds,
17130
16959
  source: "AUTO_MVS" /* AutoMvs */
17131
16960
  });
@@ -17156,7 +16985,7 @@ var McpGQLClient = class {
17156
16985
  logDebug("[GraphQL] Calling GetUserMvsAutoFix query", {
17157
16986
  userEmail: userInfo2.email
17158
16987
  });
17159
- const result = await this.clientSdk.GetUserMvsAutoFix({
16988
+ const result = await this._clientSdk.GetUserMvsAutoFix({
17160
16989
  userEmail: userInfo2.email
17161
16990
  });
17162
16991
  logDebug("[GraphQL] GetUserMvsAutoFix successful", { result });
@@ -17199,7 +17028,7 @@ var McpGQLClient = class {
17199
17028
  codeNodes: { path: { _in: fileFilter } }
17200
17029
  };
17201
17030
  }
17202
- const resp = await this.clientSdk.GetLatestReportByRepoUrl({
17031
+ const resp = await this._clientSdk.GetLatestReportByRepoUrl({
17203
17032
  repoUrl,
17204
17033
  limit,
17205
17034
  offset,
@@ -17272,7 +17101,7 @@ var McpGQLClient = class {
17272
17101
  error: err
17273
17102
  });
17274
17103
  }
17275
- const res = await this.clientSdk.GetReportFixes({
17104
+ const res = await this._clientSdk.GetReportFixes({
17276
17105
  reportId,
17277
17106
  limit,
17278
17107
  offset,
@@ -17317,45 +17146,65 @@ async function createAuthenticatedMcpGQLClient({
17317
17146
  isBackgroundCall = false,
17318
17147
  loginContext
17319
17148
  } = {}) {
17149
+ if (isBackgroundCall) {
17150
+ AuthManager.setBrowserCooldown(MCP_TOOLS_BROWSER_COOLDOWN_MS);
17151
+ }
17320
17152
  logDebug("[GraphQL] Getting config", {
17321
17153
  apiToken: configStore.get("apiToken")
17322
17154
  });
17323
- const initialClient = new McpGQLClient({
17324
- apiKey: process.env["MOBB_API_KEY"] || process.env["API_KEY"] || // fallback for backward compatibility
17325
- configStore.get("apiToken") || "",
17326
- type: "apiKey"
17327
- });
17328
- const isApiEndpointReachable = await initialClient.isApiEndpointReachable();
17329
- logDebug("[GraphQL] API connection status", { isApiEndpointReachable });
17330
- if (!isApiEndpointReachable) {
17331
- throw new ApiConnectionError("Error: failed to reach Mobb GraphQL endpoint");
17155
+ const apiKey = process.env["MOBB_API_KEY"] || process.env["API_KEY"] || // fallback for backward compatibility
17156
+ configStore.get("apiToken") || "";
17157
+ const resolvedApiUrl = process.env["API_URL"] || void 0;
17158
+ const authManager = new AuthManager(void 0, resolvedApiUrl);
17159
+ const initialClient = new McpGQLClient({ apiKey, type: "apiKey" });
17160
+ authManager.setGQLClient(initialClient);
17161
+ const authResult = await authManager.checkAuthentication();
17162
+ if (authResult.isAuthenticated) {
17163
+ return initialClient;
17332
17164
  }
17333
- logDebug("[GraphQL] Validating user token");
17334
- let userVerify;
17335
- try {
17336
- userVerify = await initialClient.validateUserToken();
17337
- } catch (e) {
17338
- logError("[GraphQL] Token validation failed due to transient error", {
17339
- error: e
17165
+ if (authResult.reason === "unknown") {
17166
+ logError("[GraphQL] Auth check failed with transient error", {
17167
+ message: authResult.message
17340
17168
  });
17341
- throw e;
17342
- }
17343
- if (userVerify) {
17344
- return initialClient;
17169
+ throw new ApiConnectionError(
17170
+ `Cannot verify authentication: ${authResult.message}`
17171
+ );
17345
17172
  }
17346
- const authService = new McpAuthService(initialClient);
17347
- const newApiToken = await authService.authenticate(
17348
- isBackgroundCall,
17173
+ const loginUrl = await authManager.generateLoginUrl(
17174
+ "/mvs-login",
17349
17175
  loginContext
17350
17176
  );
17177
+ if (!loginUrl) {
17178
+ throw new AuthenticationError("Failed to generate login URL");
17179
+ }
17180
+ const opened = authManager.openUrlInBrowser();
17181
+ if (!opened) {
17182
+ throw new AuthenticationError(
17183
+ "Authentication required but browser cooldown is active"
17184
+ );
17185
+ }
17186
+ const newApiToken = await authManager.waitForApiToken();
17187
+ if (!newApiToken) {
17188
+ throw new FailedToGetApiTokenError(
17189
+ `Login timeout: authentication not completed within ${AuthManager.loginMaxWait / 1e3 / 60} minutes`
17190
+ );
17191
+ }
17192
+ const authenticatedClient = new McpGQLClient({
17193
+ apiKey: newApiToken,
17194
+ type: "apiKey"
17195
+ });
17196
+ const loginSuccess = await authenticatedClient.validateUserToken();
17197
+ if (!loginSuccess) {
17198
+ throw new AuthenticationError("Login failed: token validation failed");
17199
+ }
17351
17200
  configStore.set("apiToken", newApiToken);
17352
- return new McpGQLClient({ apiKey: newApiToken, type: "apiKey" });
17201
+ return authenticatedClient;
17353
17202
  }
17354
17203
 
17355
17204
  // src/mcp/services/McpUsageService/host.ts
17356
17205
  import { execSync as execSync2 } from "child_process";
17357
17206
  import fs13 from "fs";
17358
- import os6 from "os";
17207
+ import os5 from "os";
17359
17208
  import path14 from "path";
17360
17209
  var IDEs = ["cursor", "windsurf", "webstorm", "vscode", "claude"];
17361
17210
  var runCommand = (cmd) => {
@@ -17370,7 +17219,7 @@ var gitInfo = {
17370
17219
  email: runCommand("git config user.email")
17371
17220
  };
17372
17221
  var getClaudeWorkspacePaths = () => {
17373
- const home = os6.homedir();
17222
+ const home = os5.homedir();
17374
17223
  const claudeIdePath = path14.join(home, ".claude", "ide");
17375
17224
  const workspacePaths = [];
17376
17225
  if (!fs13.existsSync(claudeIdePath)) {
@@ -17399,7 +17248,7 @@ var getClaudeWorkspacePaths = () => {
17399
17248
  return workspacePaths;
17400
17249
  };
17401
17250
  var getMCPConfigPaths = (hostName) => {
17402
- const home = os6.homedir();
17251
+ const home = os5.homedir();
17403
17252
  const currentDir = process.env["WORKSPACE_FOLDER_PATHS"] || process.env["PWD"] || process.cwd();
17404
17253
  switch (hostName.toLowerCase()) {
17405
17254
  case "cursor":
@@ -17489,7 +17338,7 @@ var readMCPConfig = (hostName) => {
17489
17338
  };
17490
17339
  var getRunningProcesses = () => {
17491
17340
  try {
17492
- return os6.platform() === "win32" ? execSync2("tasklist", { encoding: "utf8" }) : execSync2("ps aux", { encoding: "utf8" });
17341
+ return os5.platform() === "win32" ? execSync2("tasklist", { encoding: "utf8" }) : execSync2("ps aux", { encoding: "utf8" });
17493
17342
  } catch {
17494
17343
  return "";
17495
17344
  }
@@ -17564,7 +17413,7 @@ var versionCommands = {
17564
17413
  }
17565
17414
  };
17566
17415
  var getProcessInfo = (pid) => {
17567
- const platform2 = os6.platform();
17416
+ const platform2 = os5.platform();
17568
17417
  try {
17569
17418
  if (platform2 === "linux" || platform2 === "darwin") {
17570
17419
  const output = execSync2(`ps -o pid=,ppid=,comm= -p ${pid}`, {
@@ -17683,7 +17532,7 @@ var getHostInfo = (additionalMcpList) => {
17683
17532
  const config2 = allConfigs[ide] || null;
17684
17533
  const ideName = ide.charAt(0).toUpperCase() + ide.slice(1) || "Unknown";
17685
17534
  let ideVersion = "Unknown";
17686
- const platform2 = os6.platform();
17535
+ const platform2 = os5.platform();
17687
17536
  const cmds = versionCommands[ideName]?.[platform2] ?? [];
17688
17537
  for (const cmd of cmds) {
17689
17538
  try {
@@ -17716,14 +17565,14 @@ var getHostInfo = (additionalMcpList) => {
17716
17565
 
17717
17566
  // src/mcp/services/McpUsageService/McpUsageService.ts
17718
17567
  import fetch5 from "node-fetch";
17719
- import os8 from "os";
17720
- import { v4 as uuidv43, v5 as uuidv5 } from "uuid";
17568
+ import os7 from "os";
17569
+ import { v4 as uuidv42, v5 as uuidv5 } from "uuid";
17721
17570
  init_configs();
17722
17571
 
17723
17572
  // src/mcp/services/McpUsageService/system.ts
17724
17573
  init_configs();
17725
17574
  import fs14 from "fs";
17726
- import os7 from "os";
17575
+ import os6 from "os";
17727
17576
  import path15 from "path";
17728
17577
  var MAX_DEPTH = 2;
17729
17578
  var patterns = ["mcp", "claude"];
@@ -17758,8 +17607,8 @@ var searchDir = async (dir, depth = 0) => {
17758
17607
  };
17759
17608
  var findSystemMCPConfigs = async () => {
17760
17609
  try {
17761
- const home = os7.homedir();
17762
- const platform2 = os7.platform();
17610
+ const home = os6.homedir();
17611
+ const platform2 = os6.platform();
17763
17612
  const knownDirs = platform2 === "win32" ? [
17764
17613
  path15.join(home, ".cursor"),
17765
17614
  path15.join(home, "Documents"),
@@ -17831,7 +17680,7 @@ var McpUsageService = class {
17831
17680
  generateHostId() {
17832
17681
  const stored = configStore.get(this.configKey);
17833
17682
  if (stored?.mcpHostId) return stored.mcpHostId;
17834
- const interfaces = os8.networkInterfaces();
17683
+ const interfaces = os7.networkInterfaces();
17835
17684
  const macs = [];
17836
17685
  for (const iface of Object.values(interfaces)) {
17837
17686
  if (!iface) continue;
@@ -17839,7 +17688,7 @@ var McpUsageService = class {
17839
17688
  if (net.mac && net.mac !== "00:00:00:00:00:00") macs.push(net.mac);
17840
17689
  }
17841
17690
  }
17842
- const macString = macs.length ? macs.sort().join(",") : `${os8.hostname()}-${uuidv43()}`;
17691
+ const macString = macs.length ? macs.sort().join(",") : `${os7.hostname()}-${uuidv42()}`;
17843
17692
  const hostId = uuidv5(macString, uuidv5.DNS);
17844
17693
  logDebug("[UsageService] Generated new host ID", { hostId });
17845
17694
  return hostId;
@@ -17862,7 +17711,7 @@ var McpUsageService = class {
17862
17711
  mcpHostId,
17863
17712
  organizationId,
17864
17713
  mcpVersion: packageJson.version,
17865
- mcpOsName: os8.platform(),
17714
+ mcpOsName: os7.platform(),
17866
17715
  mcps: JSON.stringify(mcps),
17867
17716
  status,
17868
17717
  userName: user.name,
@@ -20183,7 +20032,7 @@ For a complete security audit workflow, use the \`full-security-audit\` prompt.
20183
20032
 
20184
20033
  // src/mcp/services/McpDetectionService/CursorMcpDetectionService.ts
20185
20034
  import * as fs17 from "fs";
20186
- import * as os10 from "os";
20035
+ import * as os9 from "os";
20187
20036
  import * as path17 from "path";
20188
20037
 
20189
20038
  // src/mcp/services/McpDetectionService/BaseMcpDetectionService.ts
@@ -20194,11 +20043,11 @@ import * as path16 from "path";
20194
20043
 
20195
20044
  // src/mcp/services/McpDetectionService/McpDetectionServiceUtils.ts
20196
20045
  import * as fs15 from "fs";
20197
- import * as os9 from "os";
20046
+ import * as os8 from "os";
20198
20047
 
20199
20048
  // src/mcp/services/McpDetectionService/VscodeMcpDetectionService.ts
20200
20049
  import * as fs18 from "fs";
20201
- import * as os11 from "os";
20050
+ import * as os10 from "os";
20202
20051
  import * as path18 from "path";
20203
20052
 
20204
20053
  // src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesTool.ts
@@ -24643,18 +24492,18 @@ async function getGrpcClient(port, csrf3) {
24643
24492
 
24644
24493
  // src/features/codeium_intellij/parse_intellij_logs.ts
24645
24494
  import fs25 from "fs";
24646
- import os12 from "os";
24495
+ import os11 from "os";
24647
24496
  import path24 from "path";
24648
24497
  function getLogsDir() {
24649
24498
  if (process.platform === "darwin") {
24650
- return path24.join(os12.homedir(), "Library/Logs/JetBrains");
24499
+ return path24.join(os11.homedir(), "Library/Logs/JetBrains");
24651
24500
  } else if (process.platform === "win32") {
24652
24501
  return path24.join(
24653
- process.env["LOCALAPPDATA"] || path24.join(os12.homedir(), "AppData/Local"),
24502
+ process.env["LOCALAPPDATA"] || path24.join(os11.homedir(), "AppData/Local"),
24654
24503
  "JetBrains"
24655
24504
  );
24656
24505
  } else {
24657
- return path24.join(os12.homedir(), ".cache/JetBrains");
24506
+ return path24.join(os11.homedir(), ".cache/JetBrains");
24658
24507
  }
24659
24508
  }
24660
24509
  function parseIdeLogDir(ideLogDir) {
@@ -24877,11 +24726,11 @@ function processChatStepCodeAction(step) {
24877
24726
 
24878
24727
  // src/features/codeium_intellij/install_hook.ts
24879
24728
  import fsPromises6 from "fs/promises";
24880
- import os13 from "os";
24729
+ import os12 from "os";
24881
24730
  import path25 from "path";
24882
24731
  import chalk14 from "chalk";
24883
24732
  function getCodeiumHooksPath() {
24884
- return path25.join(os13.homedir(), ".codeium", "hooks.json");
24733
+ return path25.join(os12.homedir(), ".codeium", "hooks.json");
24885
24734
  }
24886
24735
  async function readCodeiumHooks() {
24887
24736
  const hooksPath = getCodeiumHooksPath();