mobbdev 1.2.34 → 1.2.36
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/args/commands/upload_ai_blame.mjs +40 -15
- package/dist/index.mjs +108 -38
- package/package.json +1 -1
|
@@ -2255,20 +2255,20 @@ function computeCanonicalUrl(data) {
|
|
|
2255
2255
|
} = data;
|
|
2256
2256
|
switch (scmType) {
|
|
2257
2257
|
case "GitHub" /* GitHub */:
|
|
2258
|
-
return `https://${hostname}/${organization}/${repoName}`;
|
|
2258
|
+
return `https://${hostname}/${organization.toLowerCase()}/${repoName.toLowerCase()}`;
|
|
2259
2259
|
case "GitLab" /* GitLab */:
|
|
2260
|
-
return `https://${hostname}/${projectPath}`;
|
|
2260
|
+
return `https://${hostname}/${projectPath.toLowerCase()}`;
|
|
2261
2261
|
case "Bitbucket" /* Bitbucket */:
|
|
2262
|
-
return `https://${hostname}/${organization}/${repoName}`;
|
|
2262
|
+
return `https://${hostname}/${organization.toLowerCase()}/${repoName.toLowerCase()}`;
|
|
2263
2263
|
case "Ado" /* Ado */: {
|
|
2264
2264
|
const adoHostname = hostname === "ssh.dev.azure.com" ? "dev.azure.com" : hostname;
|
|
2265
2265
|
if (projectName) {
|
|
2266
|
-
return `https://${adoHostname}/${organization}/${projectName}/_git/${repoName}`;
|
|
2266
|
+
return `https://${adoHostname}/${organization.toLowerCase()}/${projectName.toLowerCase()}/_git/${repoName.toLowerCase()}`;
|
|
2267
2267
|
}
|
|
2268
|
-
return `https://${adoHostname}/${organization}/_git/${repoName}`;
|
|
2268
|
+
return `https://${adoHostname}/${organization.toLowerCase()}/_git/${repoName.toLowerCase()}`;
|
|
2269
2269
|
}
|
|
2270
2270
|
default:
|
|
2271
|
-
return `https://${hostname}/${projectPath}`;
|
|
2271
|
+
return `https://${hostname}/${projectPath.toLowerCase()}`;
|
|
2272
2272
|
}
|
|
2273
2273
|
}
|
|
2274
2274
|
function detectAdoUrl(args) {
|
|
@@ -4333,7 +4333,7 @@ var VUL_REPORT_DIGEST_TIMEOUT_MS = 1e3 * 60 * 30;
|
|
|
4333
4333
|
|
|
4334
4334
|
// src/features/analysis/graphql/gql.ts
|
|
4335
4335
|
import Debug6 from "debug";
|
|
4336
|
-
import { GraphQLClient } from "graphql-request";
|
|
4336
|
+
import { ClientError, GraphQLClient } from "graphql-request";
|
|
4337
4337
|
import { v4 as uuidv4 } from "uuid";
|
|
4338
4338
|
|
|
4339
4339
|
// src/mcp/core/Errors.ts
|
|
@@ -6691,6 +6691,19 @@ var GetVulByNodesMetadataZ = z26.object({
|
|
|
6691
6691
|
|
|
6692
6692
|
// src/features/analysis/graphql/gql.ts
|
|
6693
6693
|
var debug7 = Debug6("mobbdev:gql");
|
|
6694
|
+
function isAuthError(error) {
|
|
6695
|
+
if (error instanceof ClientError) {
|
|
6696
|
+
const gqlErrors = error.response?.errors;
|
|
6697
|
+
return gqlErrors?.some(
|
|
6698
|
+
(e) => e.extensions?.["code"] === "access-denied" || e.message?.includes("Authentication hook unauthorized")
|
|
6699
|
+
) ?? false;
|
|
6700
|
+
}
|
|
6701
|
+
return false;
|
|
6702
|
+
}
|
|
6703
|
+
function isNetworkError(error) {
|
|
6704
|
+
const errorString = error?.toString() ?? "";
|
|
6705
|
+
return errorString.includes("FetchError") || errorString.includes("TypeError") || errorString.includes("ECONNREFUSED") || errorString.includes("ENOTFOUND") || errorString.includes("ETIMEDOUT") || errorString.includes("UND_ERR");
|
|
6706
|
+
}
|
|
6694
6707
|
var API_KEY_HEADER_NAME = "x-mobb-key";
|
|
6695
6708
|
var REPORT_STATE_CHECK_DELAY = 5 * 1e3;
|
|
6696
6709
|
var GQLClient = class {
|
|
@@ -6750,23 +6763,35 @@ var GQLClient = class {
|
|
|
6750
6763
|
try {
|
|
6751
6764
|
await this.getUserInfo();
|
|
6752
6765
|
} catch (e) {
|
|
6753
|
-
if (e
|
|
6754
|
-
debug7("verify connection failed %o", e);
|
|
6766
|
+
if (isNetworkError(e)) {
|
|
6767
|
+
debug7("verify connection failed (network error) %o", e);
|
|
6755
6768
|
return false;
|
|
6756
6769
|
}
|
|
6770
|
+
debug7("verify connection: endpoint reachable but request failed %o", e);
|
|
6757
6771
|
}
|
|
6758
6772
|
return true;
|
|
6759
6773
|
}
|
|
6760
6774
|
async validateUserToken() {
|
|
6761
|
-
await this.createCommunityUser();
|
|
6762
|
-
let info;
|
|
6763
6775
|
try {
|
|
6764
|
-
|
|
6776
|
+
await this.createCommunityUser();
|
|
6777
|
+
const info = await this.getUserInfo();
|
|
6778
|
+
if (!info) {
|
|
6779
|
+
debug7("verify token failed - no user info returned");
|
|
6780
|
+
return false;
|
|
6781
|
+
}
|
|
6782
|
+
return info.email || true;
|
|
6765
6783
|
} catch (e) {
|
|
6766
|
-
|
|
6767
|
-
|
|
6784
|
+
if (isAuthError(e)) {
|
|
6785
|
+
debug7("verify token failed - auth error %o", e);
|
|
6786
|
+
return false;
|
|
6787
|
+
}
|
|
6788
|
+
if (isNetworkError(e)) {
|
|
6789
|
+
debug7("verify token failed - network error, rethrowing %o", e);
|
|
6790
|
+
throw e;
|
|
6791
|
+
}
|
|
6792
|
+
debug7("verify token failed - unexpected error, rethrowing %o", e);
|
|
6793
|
+
throw e;
|
|
6768
6794
|
}
|
|
6769
|
-
return info?.email || true;
|
|
6770
6795
|
}
|
|
6771
6796
|
async getLastOrgAndNamedProject(params) {
|
|
6772
6797
|
const me = await this.getUserInfo();
|
package/dist/index.mjs
CHANGED
|
@@ -2304,20 +2304,20 @@ function computeCanonicalUrl(data) {
|
|
|
2304
2304
|
} = data;
|
|
2305
2305
|
switch (scmType) {
|
|
2306
2306
|
case "GitHub" /* GitHub */:
|
|
2307
|
-
return `https://${hostname}/${organization}/${repoName}`;
|
|
2307
|
+
return `https://${hostname}/${organization.toLowerCase()}/${repoName.toLowerCase()}`;
|
|
2308
2308
|
case "GitLab" /* GitLab */:
|
|
2309
|
-
return `https://${hostname}/${projectPath}`;
|
|
2309
|
+
return `https://${hostname}/${projectPath.toLowerCase()}`;
|
|
2310
2310
|
case "Bitbucket" /* Bitbucket */:
|
|
2311
|
-
return `https://${hostname}/${organization}/${repoName}`;
|
|
2311
|
+
return `https://${hostname}/${organization.toLowerCase()}/${repoName.toLowerCase()}`;
|
|
2312
2312
|
case "Ado" /* Ado */: {
|
|
2313
2313
|
const adoHostname = hostname === "ssh.dev.azure.com" ? "dev.azure.com" : hostname;
|
|
2314
2314
|
if (projectName) {
|
|
2315
|
-
return `https://${adoHostname}/${organization}/${projectName}/_git/${repoName}`;
|
|
2315
|
+
return `https://${adoHostname}/${organization.toLowerCase()}/${projectName.toLowerCase()}/_git/${repoName.toLowerCase()}`;
|
|
2316
2316
|
}
|
|
2317
|
-
return `https://${adoHostname}/${organization}/_git/${repoName}`;
|
|
2317
|
+
return `https://${adoHostname}/${organization.toLowerCase()}/_git/${repoName.toLowerCase()}`;
|
|
2318
2318
|
}
|
|
2319
2319
|
default:
|
|
2320
|
-
return `https://${hostname}/${projectPath}`;
|
|
2320
|
+
return `https://${hostname}/${projectPath.toLowerCase()}`;
|
|
2321
2321
|
}
|
|
2322
2322
|
}
|
|
2323
2323
|
function detectAdoUrl(args) {
|
|
@@ -11761,7 +11761,7 @@ import open from "open";
|
|
|
11761
11761
|
|
|
11762
11762
|
// src/features/analysis/graphql/gql.ts
|
|
11763
11763
|
import Debug6 from "debug";
|
|
11764
|
-
import { GraphQLClient } from "graphql-request";
|
|
11764
|
+
import { ClientError, GraphQLClient } from "graphql-request";
|
|
11765
11765
|
import { v4 as uuidv4 } from "uuid";
|
|
11766
11766
|
|
|
11767
11767
|
// src/mcp/core/Errors.ts
|
|
@@ -12097,6 +12097,19 @@ var GetVulByNodesMetadataZ = z26.object({
|
|
|
12097
12097
|
|
|
12098
12098
|
// src/features/analysis/graphql/gql.ts
|
|
12099
12099
|
var debug7 = Debug6("mobbdev:gql");
|
|
12100
|
+
function isAuthError(error) {
|
|
12101
|
+
if (error instanceof ClientError) {
|
|
12102
|
+
const gqlErrors = error.response?.errors;
|
|
12103
|
+
return gqlErrors?.some(
|
|
12104
|
+
(e) => e.extensions?.["code"] === "access-denied" || e.message?.includes("Authentication hook unauthorized")
|
|
12105
|
+
) ?? false;
|
|
12106
|
+
}
|
|
12107
|
+
return false;
|
|
12108
|
+
}
|
|
12109
|
+
function isNetworkError(error) {
|
|
12110
|
+
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");
|
|
12112
|
+
}
|
|
12100
12113
|
var API_KEY_HEADER_NAME = "x-mobb-key";
|
|
12101
12114
|
var REPORT_STATE_CHECK_DELAY = 5 * 1e3;
|
|
12102
12115
|
var GQLClient = class {
|
|
@@ -12156,23 +12169,35 @@ var GQLClient = class {
|
|
|
12156
12169
|
try {
|
|
12157
12170
|
await this.getUserInfo();
|
|
12158
12171
|
} catch (e) {
|
|
12159
|
-
if (e
|
|
12160
|
-
debug7("verify connection failed %o", e);
|
|
12172
|
+
if (isNetworkError(e)) {
|
|
12173
|
+
debug7("verify connection failed (network error) %o", e);
|
|
12161
12174
|
return false;
|
|
12162
12175
|
}
|
|
12176
|
+
debug7("verify connection: endpoint reachable but request failed %o", e);
|
|
12163
12177
|
}
|
|
12164
12178
|
return true;
|
|
12165
12179
|
}
|
|
12166
12180
|
async validateUserToken() {
|
|
12167
|
-
await this.createCommunityUser();
|
|
12168
|
-
let info;
|
|
12169
12181
|
try {
|
|
12170
|
-
|
|
12182
|
+
await this.createCommunityUser();
|
|
12183
|
+
const info = await this.getUserInfo();
|
|
12184
|
+
if (!info) {
|
|
12185
|
+
debug7("verify token failed - no user info returned");
|
|
12186
|
+
return false;
|
|
12187
|
+
}
|
|
12188
|
+
return info.email || true;
|
|
12171
12189
|
} catch (e) {
|
|
12172
|
-
|
|
12173
|
-
|
|
12190
|
+
if (isAuthError(e)) {
|
|
12191
|
+
debug7("verify token failed - auth error %o", e);
|
|
12192
|
+
return false;
|
|
12193
|
+
}
|
|
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);
|
|
12199
|
+
throw e;
|
|
12174
12200
|
}
|
|
12175
|
-
return info?.email || true;
|
|
12176
12201
|
}
|
|
12177
12202
|
async getLastOrgAndNamedProject(params) {
|
|
12178
12203
|
const me = await this.getUserInfo();
|
|
@@ -16405,7 +16430,7 @@ var log = logger.log.bind(logger);
|
|
|
16405
16430
|
|
|
16406
16431
|
// src/mcp/services/McpGQLClient.ts
|
|
16407
16432
|
import crypto3 from "crypto";
|
|
16408
|
-
import { GraphQLClient as GraphQLClient2 } from "graphql-request";
|
|
16433
|
+
import { ClientError as ClientError2, GraphQLClient as GraphQLClient2 } from "graphql-request";
|
|
16409
16434
|
import { v4 as uuidv42 } from "uuid";
|
|
16410
16435
|
init_client_generates();
|
|
16411
16436
|
init_configs();
|
|
@@ -16544,12 +16569,15 @@ import crypto2 from "crypto";
|
|
|
16544
16569
|
import os5 from "os";
|
|
16545
16570
|
import open4 from "open";
|
|
16546
16571
|
init_configs();
|
|
16547
|
-
var
|
|
16572
|
+
var _McpAuthService = class _McpAuthService {
|
|
16548
16573
|
constructor(client) {
|
|
16549
16574
|
__publicField(this, "client");
|
|
16550
|
-
__publicField(this, "lastBrowserOpenTime", 0);
|
|
16551
16575
|
this.client = client;
|
|
16552
16576
|
}
|
|
16577
|
+
/** Reset cooldown state. Used by tests to ensure isolation. */
|
|
16578
|
+
static resetCooldown() {
|
|
16579
|
+
_McpAuthService.lastBrowserOpenTime = 0;
|
|
16580
|
+
}
|
|
16553
16581
|
/**
|
|
16554
16582
|
* Opens a browser window for authentication
|
|
16555
16583
|
* @param url URL to open in browser
|
|
@@ -16558,14 +16586,15 @@ var McpAuthService = class {
|
|
|
16558
16586
|
async openBrowser(url, isBackgoundCall) {
|
|
16559
16587
|
if (isBackgoundCall) {
|
|
16560
16588
|
const now = Date.now();
|
|
16561
|
-
if (now -
|
|
16589
|
+
if (now - _McpAuthService.lastBrowserOpenTime < MCP_TOOLS_BROWSER_COOLDOWN_MS) {
|
|
16562
16590
|
logDebug(`browser cooldown active, skipping open for ${url}`);
|
|
16563
|
-
return;
|
|
16591
|
+
return false;
|
|
16564
16592
|
}
|
|
16565
16593
|
}
|
|
16566
16594
|
logDebug(`opening browser url ${url}`);
|
|
16567
16595
|
await open4(url);
|
|
16568
|
-
|
|
16596
|
+
_McpAuthService.lastBrowserOpenTime = Date.now();
|
|
16597
|
+
return true;
|
|
16569
16598
|
}
|
|
16570
16599
|
/**
|
|
16571
16600
|
* Handles the complete authentication flow
|
|
@@ -16587,7 +16616,12 @@ var McpAuthService = class {
|
|
|
16587
16616
|
logDebug(`cli login created ${loginId}`);
|
|
16588
16617
|
const webLoginUrl = `${WEB_APP_URL}/mvs-login`;
|
|
16589
16618
|
const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, loginId, os5.hostname(), loginContext) : `${webLoginUrl}/${loginId}?hostname=${os5.hostname()}`;
|
|
16590
|
-
await this.openBrowser(browserUrl, isBackgoundCall);
|
|
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
|
+
}
|
|
16591
16625
|
logDebug(`waiting for login to complete`);
|
|
16592
16626
|
let newApiToken = null;
|
|
16593
16627
|
for (let i = 0; i < MCP_LOGIN_MAX_WAIT / MCP_LOGIN_CHECK_DELAY; i++) {
|
|
@@ -16618,8 +16652,24 @@ var McpAuthService = class {
|
|
|
16618
16652
|
return newApiToken;
|
|
16619
16653
|
}
|
|
16620
16654
|
};
|
|
16655
|
+
// Static so cooldown persists across McpAuthService instances
|
|
16656
|
+
__publicField(_McpAuthService, "lastBrowserOpenTime", 0);
|
|
16657
|
+
var McpAuthService = _McpAuthService;
|
|
16621
16658
|
|
|
16622
16659
|
// 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
|
+
}
|
|
16623
16673
|
var McpGQLClient = class {
|
|
16624
16674
|
constructor(args) {
|
|
16625
16675
|
__publicField(this, "client");
|
|
@@ -16673,14 +16723,16 @@ var McpGQLClient = class {
|
|
|
16673
16723
|
logDebug("[GraphQL] Me query successful", { result });
|
|
16674
16724
|
return true;
|
|
16675
16725
|
} catch (e) {
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
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
|
+
});
|
|
16680
16731
|
return false;
|
|
16681
16732
|
}
|
|
16733
|
+
logDebug("[GraphQL] API endpoint is reachable (non-network error)");
|
|
16734
|
+
return true;
|
|
16682
16735
|
}
|
|
16683
|
-
return true;
|
|
16684
16736
|
}
|
|
16685
16737
|
/**
|
|
16686
16738
|
* Verifies both API endpoint reachability and user authentication
|
|
@@ -16951,11 +17003,27 @@ var McpGQLClient = class {
|
|
|
16951
17003
|
try {
|
|
16952
17004
|
await this.clientSdk.CreateCommunityUser();
|
|
16953
17005
|
const info = await this.getUserInfo();
|
|
17006
|
+
if (!info) {
|
|
17007
|
+
logDebug("[GraphQL] User token is invalid (no user info returned)");
|
|
17008
|
+
return false;
|
|
17009
|
+
}
|
|
16954
17010
|
logDebug("[GraphQL] User token validated successfully");
|
|
16955
|
-
return info
|
|
17011
|
+
return info.email || true;
|
|
16956
17012
|
} catch (e) {
|
|
16957
|
-
|
|
16958
|
-
|
|
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;
|
|
16959
17027
|
}
|
|
16960
17028
|
}
|
|
16961
17029
|
async createCliLogin(variables) {
|
|
@@ -17263,7 +17331,15 @@ async function createAuthenticatedMcpGQLClient({
|
|
|
17263
17331
|
throw new ApiConnectionError("Error: failed to reach Mobb GraphQL endpoint");
|
|
17264
17332
|
}
|
|
17265
17333
|
logDebug("[GraphQL] Validating user token");
|
|
17266
|
-
|
|
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
|
|
17340
|
+
});
|
|
17341
|
+
throw e;
|
|
17342
|
+
}
|
|
17267
17343
|
if (userVerify) {
|
|
17268
17344
|
return initialClient;
|
|
17269
17345
|
}
|
|
@@ -20214,13 +20290,7 @@ var BaseTool = class {
|
|
|
20214
20290
|
}
|
|
20215
20291
|
async execute(args) {
|
|
20216
20292
|
if (this.hasAuthentication) {
|
|
20217
|
-
logDebug(`
|
|
20218
|
-
const loginContext = createMcpLoginContext(this.name);
|
|
20219
|
-
const mcpGqlClient = await createAuthenticatedMcpGQLClient({
|
|
20220
|
-
loginContext
|
|
20221
|
-
});
|
|
20222
|
-
const userInfo2 = await mcpGqlClient.getUserInfo();
|
|
20223
|
-
logDebug("User authenticated successfully", { userInfo: userInfo2 });
|
|
20293
|
+
logDebug(`Tool ${this.name} requires authentication (handled by service)`);
|
|
20224
20294
|
}
|
|
20225
20295
|
const validatedArgs = this.validateInput(args);
|
|
20226
20296
|
logDebug(`Tool ${this.name} input validation successful`, {
|