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/args/commands/upload_ai_blame.mjs +116 -68
- package/dist/index.mjs +365 -516
- package/package.json +1 -1
|
@@ -6700,9 +6700,19 @@ function isAuthError(error) {
|
|
|
6700
6700
|
}
|
|
6701
6701
|
return false;
|
|
6702
6702
|
}
|
|
6703
|
-
function
|
|
6703
|
+
function isTransientError(error) {
|
|
6704
6704
|
const errorString = error?.toString() ?? "";
|
|
6705
|
-
|
|
6705
|
+
if (errorString.includes("FetchError") || errorString.includes("TypeError") || errorString.includes("ECONNREFUSED") || errorString.includes("ENOTFOUND") || errorString.includes("ETIMEDOUT") || errorString.includes("UND_ERR")) {
|
|
6706
|
+
return true;
|
|
6707
|
+
}
|
|
6708
|
+
if (error instanceof ClientError) {
|
|
6709
|
+
const status = error.response?.status;
|
|
6710
|
+
if (status && status >= 502 && status <= 504) return true;
|
|
6711
|
+
}
|
|
6712
|
+
if (errorString.includes("Gateway Time-out") || errorString.includes("Bad Gateway") || errorString.includes("Service Unavailable")) {
|
|
6713
|
+
return true;
|
|
6714
|
+
}
|
|
6715
|
+
return false;
|
|
6706
6716
|
}
|
|
6707
6717
|
var API_KEY_HEADER_NAME = "x-mobb-key";
|
|
6708
6718
|
var REPORT_STATE_CHECK_DELAY = 5 * 1e3;
|
|
@@ -6763,8 +6773,8 @@ var GQLClient = class {
|
|
|
6763
6773
|
try {
|
|
6764
6774
|
await this.getUserInfo();
|
|
6765
6775
|
} catch (e) {
|
|
6766
|
-
if (
|
|
6767
|
-
debug7("verify connection failed (
|
|
6776
|
+
if (isTransientError(e)) {
|
|
6777
|
+
debug7("verify connection failed (transient error) %o", e);
|
|
6768
6778
|
return false;
|
|
6769
6779
|
}
|
|
6770
6780
|
debug7("verify connection: endpoint reachable but request failed %o", e);
|
|
@@ -6785,11 +6795,7 @@ var GQLClient = class {
|
|
|
6785
6795
|
debug7("verify token failed - auth error %o", e);
|
|
6786
6796
|
return false;
|
|
6787
6797
|
}
|
|
6788
|
-
|
|
6789
|
-
debug7("verify token failed - network error, rethrowing %o", e);
|
|
6790
|
-
throw e;
|
|
6791
|
-
}
|
|
6792
|
-
debug7("verify token failed - unexpected error, rethrowing %o", e);
|
|
6798
|
+
debug7("verify token failed - transient/unknown error, rethrowing %o", e);
|
|
6793
6799
|
throw e;
|
|
6794
6800
|
}
|
|
6795
6801
|
}
|
|
@@ -6846,11 +6852,7 @@ var GQLClient = class {
|
|
|
6846
6852
|
return res?.cli_login_by_pk?.encryptedApiToken || null;
|
|
6847
6853
|
}
|
|
6848
6854
|
async createCommunityUser() {
|
|
6849
|
-
|
|
6850
|
-
await this._clientSdk.CreateCommunityUser();
|
|
6851
|
-
} catch (e) {
|
|
6852
|
-
debug7("create community user failed %o", e);
|
|
6853
|
-
}
|
|
6855
|
+
await this._clientSdk.CreateCommunityUser();
|
|
6854
6856
|
}
|
|
6855
6857
|
async updateScmToken(args) {
|
|
6856
6858
|
const { scmType, url, token, org, refreshToken } = args;
|
|
@@ -7406,8 +7408,8 @@ var configStore = getConfigStore();
|
|
|
7406
7408
|
|
|
7407
7409
|
// src/commands/AuthManager.ts
|
|
7408
7410
|
var debug10 = Debug9("mobbdev:auth");
|
|
7409
|
-
var LOGIN_MAX_WAIT =
|
|
7410
|
-
var LOGIN_CHECK_DELAY =
|
|
7411
|
+
var LOGIN_MAX_WAIT = 2 * 60 * 1e3;
|
|
7412
|
+
var LOGIN_CHECK_DELAY = 2 * 1e3;
|
|
7411
7413
|
var _AuthManager = class _AuthManager {
|
|
7412
7414
|
constructor(webAppUrl, apiUrl) {
|
|
7413
7415
|
__publicField(this, "publicKey");
|
|
@@ -7421,22 +7423,33 @@ var _AuthManager = class _AuthManager {
|
|
|
7421
7423
|
this.resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
|
|
7422
7424
|
this.resolvedApiUrl = apiUrl || API_URL;
|
|
7423
7425
|
}
|
|
7426
|
+
/** Set the minimum interval between browser opens (MCP sets this to 24h) */
|
|
7427
|
+
static setBrowserCooldown(ms) {
|
|
7428
|
+
_AuthManager.browserCooldownMs = ms;
|
|
7429
|
+
}
|
|
7430
|
+
/** Reset cooldown state. Used by tests to ensure isolation. */
|
|
7431
|
+
static resetCooldown() {
|
|
7432
|
+
_AuthManager.lastBrowserOpenTime = 0;
|
|
7433
|
+
_AuthManager.browserCooldownMs = 0;
|
|
7434
|
+
}
|
|
7424
7435
|
openUrlInBrowser() {
|
|
7425
|
-
if (this.currentBrowserUrl) {
|
|
7426
|
-
|
|
7427
|
-
return true;
|
|
7436
|
+
if (!this.currentBrowserUrl) {
|
|
7437
|
+
return false;
|
|
7428
7438
|
}
|
|
7429
|
-
|
|
7439
|
+
if (_AuthManager.browserCooldownMs > 0 && Date.now() - _AuthManager.lastBrowserOpenTime < _AuthManager.browserCooldownMs) {
|
|
7440
|
+
debug10("browser cooldown active, skipping open");
|
|
7441
|
+
return false;
|
|
7442
|
+
}
|
|
7443
|
+
open(this.currentBrowserUrl);
|
|
7444
|
+
_AuthManager.lastBrowserOpenTime = Date.now();
|
|
7445
|
+
return true;
|
|
7430
7446
|
}
|
|
7447
|
+
/**
|
|
7448
|
+
* Polls for the encrypted API token, decrypts it, validates it, and stores it.
|
|
7449
|
+
* Returns true if login succeeded.
|
|
7450
|
+
*/
|
|
7431
7451
|
async waitForAuthentication() {
|
|
7432
|
-
|
|
7433
|
-
for (let i = 0; i < _AuthManager.loginMaxWait / LOGIN_CHECK_DELAY; i++) {
|
|
7434
|
-
newApiToken = await this.getApiToken();
|
|
7435
|
-
if (newApiToken) {
|
|
7436
|
-
break;
|
|
7437
|
-
}
|
|
7438
|
-
await sleep(LOGIN_CHECK_DELAY);
|
|
7439
|
-
}
|
|
7452
|
+
const newApiToken = await this.waitForApiToken();
|
|
7440
7453
|
if (!newApiToken) {
|
|
7441
7454
|
return false;
|
|
7442
7455
|
}
|
|
@@ -7454,52 +7467,75 @@ var _AuthManager = class _AuthManager {
|
|
|
7454
7467
|
return false;
|
|
7455
7468
|
}
|
|
7456
7469
|
/**
|
|
7457
|
-
*
|
|
7470
|
+
* Polls for the encrypted API token and decrypts it.
|
|
7471
|
+
* Does NOT validate or store the token — use this when the caller
|
|
7472
|
+
* needs to validate on a specific client type (e.g. McpGQLClient).
|
|
7473
|
+
*/
|
|
7474
|
+
async waitForApiToken() {
|
|
7475
|
+
for (let i = 0; i < _AuthManager.loginMaxWait / LOGIN_CHECK_DELAY; i++) {
|
|
7476
|
+
const token = await this.getApiToken();
|
|
7477
|
+
if (token) {
|
|
7478
|
+
return token;
|
|
7479
|
+
}
|
|
7480
|
+
await sleep(LOGIN_CHECK_DELAY);
|
|
7481
|
+
}
|
|
7482
|
+
return null;
|
|
7483
|
+
}
|
|
7484
|
+
/**
|
|
7485
|
+
* Checks if the user is already authenticated.
|
|
7486
|
+
* Returns the full AuthResult so callers can distinguish 'invalid' from 'unknown'.
|
|
7458
7487
|
*/
|
|
7459
7488
|
async isAuthenticated() {
|
|
7460
7489
|
if (this.authenticated === null) {
|
|
7461
7490
|
const result = await this.checkAuthentication();
|
|
7462
7491
|
this.authenticated = result.isAuthenticated;
|
|
7463
7492
|
if (!result.isAuthenticated) {
|
|
7464
|
-
debug10("isAuthenticated: false \u2014 %s", result.message);
|
|
7493
|
+
debug10("isAuthenticated: false \u2014 %s (%s)", result.message, result.reason);
|
|
7465
7494
|
}
|
|
7466
7495
|
}
|
|
7467
7496
|
return this.authenticated;
|
|
7468
7497
|
}
|
|
7469
7498
|
/**
|
|
7470
|
-
*
|
|
7499
|
+
* Full 3-state auth check returning an {@link AuthResult}.
|
|
7500
|
+
*
|
|
7501
|
+
* - `isAuthenticated: true` — token is valid.
|
|
7502
|
+
* - `reason: 'invalid'` — definitive auth failure (access-denied). Caller should trigger login.
|
|
7503
|
+
* - `reason: 'unknown'` — transient/network error. Caller should NOT trigger login.
|
|
7471
7504
|
*/
|
|
7472
7505
|
async checkAuthentication(apiKey) {
|
|
7506
|
+
if (!this.gqlClient) {
|
|
7507
|
+
this.gqlClient = this.getGQLClient(apiKey);
|
|
7508
|
+
}
|
|
7509
|
+
const isConnected = await this.gqlClient.verifyApiConnection();
|
|
7510
|
+
if (!isConnected) {
|
|
7511
|
+
return {
|
|
7512
|
+
isAuthenticated: false,
|
|
7513
|
+
reason: "unknown",
|
|
7514
|
+
message: "Failed to connect to Mobb server"
|
|
7515
|
+
};
|
|
7516
|
+
}
|
|
7473
7517
|
try {
|
|
7474
|
-
if (!this.gqlClient) {
|
|
7475
|
-
this.gqlClient = this.getGQLClient(apiKey);
|
|
7476
|
-
}
|
|
7477
|
-
const isConnected = await this.gqlClient.verifyApiConnection();
|
|
7478
|
-
if (!isConnected) {
|
|
7479
|
-
return {
|
|
7480
|
-
isAuthenticated: false,
|
|
7481
|
-
message: "Failed to connect to Mobb server"
|
|
7482
|
-
};
|
|
7483
|
-
}
|
|
7484
7518
|
const userVerify = await this.gqlClient.validateUserToken();
|
|
7485
7519
|
if (!userVerify) {
|
|
7486
7520
|
return {
|
|
7487
7521
|
isAuthenticated: false,
|
|
7522
|
+
reason: "invalid",
|
|
7488
7523
|
message: "User token validation failed"
|
|
7489
7524
|
};
|
|
7490
7525
|
}
|
|
7491
7526
|
} catch (error) {
|
|
7492
7527
|
return {
|
|
7493
7528
|
isAuthenticated: false,
|
|
7529
|
+
reason: "unknown",
|
|
7494
7530
|
message: error instanceof Error ? error.message : "Unknown authentication error"
|
|
7495
7531
|
};
|
|
7496
7532
|
}
|
|
7497
|
-
return { isAuthenticated: true
|
|
7533
|
+
return { isAuthenticated: true };
|
|
7498
7534
|
}
|
|
7499
7535
|
/**
|
|
7500
7536
|
* Generates a login URL for manual authentication
|
|
7501
7537
|
*/
|
|
7502
|
-
async generateLoginUrl(loginContext) {
|
|
7538
|
+
async generateLoginUrl(loginPath, loginContext) {
|
|
7503
7539
|
try {
|
|
7504
7540
|
if (!this.gqlClient) {
|
|
7505
7541
|
this.gqlClient = this.getGQLClient();
|
|
@@ -7512,7 +7548,7 @@ var _AuthManager = class _AuthManager {
|
|
|
7512
7548
|
this.loginId = await this.gqlClient.createCliLogin({
|
|
7513
7549
|
publicKey: this.publicKey.export({ format: "pem", type: "pkcs1" }).toString()
|
|
7514
7550
|
});
|
|
7515
|
-
const webLoginUrl = `${this.resolvedWebAppUrl}/cli-login`;
|
|
7551
|
+
const webLoginUrl = `${this.resolvedWebAppUrl}${loginPath || "/cli-login"}`;
|
|
7516
7552
|
const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, this.loginId, os.hostname(), loginContext) : `${webLoginUrl}/${this.loginId}?hostname=${os.hostname()}`;
|
|
7517
7553
|
this.currentBrowserUrl = browserUrl;
|
|
7518
7554
|
return browserUrl;
|
|
@@ -7522,22 +7558,32 @@ var _AuthManager = class _AuthManager {
|
|
|
7522
7558
|
}
|
|
7523
7559
|
}
|
|
7524
7560
|
/**
|
|
7525
|
-
* Retrieves and decrypts the API token after authentication
|
|
7561
|
+
* Retrieves and decrypts the API token after authentication.
|
|
7562
|
+
* Returns null if the token is not yet available or on transient errors.
|
|
7526
7563
|
*/
|
|
7527
7564
|
async getApiToken() {
|
|
7528
7565
|
if (!this.gqlClient || !this.loginId || !this.privateKey) {
|
|
7529
7566
|
return null;
|
|
7530
7567
|
}
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7568
|
+
try {
|
|
7569
|
+
const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
|
|
7570
|
+
loginId: this.loginId
|
|
7571
|
+
});
|
|
7572
|
+
if (encryptedApiToken) {
|
|
7573
|
+
return crypto.privateDecrypt(
|
|
7574
|
+
this.privateKey,
|
|
7575
|
+
Buffer.from(encryptedApiToken, "base64")
|
|
7576
|
+
).toString("utf-8");
|
|
7577
|
+
}
|
|
7578
|
+
return null;
|
|
7579
|
+
} catch (error) {
|
|
7580
|
+
if (isTransientError(error)) {
|
|
7581
|
+
debug10("getApiToken: transient error, will retry");
|
|
7582
|
+
} else {
|
|
7583
|
+
debug10("getApiToken: unexpected error: %O", error);
|
|
7584
|
+
}
|
|
7585
|
+
return null;
|
|
7539
7586
|
}
|
|
7540
|
-
return null;
|
|
7541
7587
|
}
|
|
7542
7588
|
/**
|
|
7543
7589
|
* Returns true if a non-empty API token is stored in the configStore.
|
|
@@ -7581,12 +7627,13 @@ var _AuthManager = class _AuthManager {
|
|
|
7581
7627
|
};
|
|
7582
7628
|
/** Maximum time (ms) to wait for login authentication. Override in tests for faster failures. */
|
|
7583
7629
|
__publicField(_AuthManager, "loginMaxWait", LOGIN_MAX_WAIT);
|
|
7630
|
+
/** Browser cooldown: minimum ms between browser opens (0 = no cooldown) */
|
|
7631
|
+
__publicField(_AuthManager, "browserCooldownMs", 0);
|
|
7632
|
+
__publicField(_AuthManager, "lastBrowserOpenTime", 0);
|
|
7584
7633
|
var AuthManager = _AuthManager;
|
|
7585
7634
|
|
|
7586
7635
|
// src/commands/handleMobbLogin.ts
|
|
7587
7636
|
var debug11 = Debug10("mobbdev:commands");
|
|
7588
|
-
var LOGIN_MAX_WAIT2 = 10 * 60 * 1e3;
|
|
7589
|
-
var LOGIN_CHECK_DELAY2 = 5 * 1e3;
|
|
7590
7637
|
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, ${chalk2.bgBlue(
|
|
7591
7638
|
"press any key to continue"
|
|
7592
7639
|
)};`;
|
|
@@ -7617,7 +7664,8 @@ async function handleMobbLogin({
|
|
|
7617
7664
|
skipPrompts,
|
|
7618
7665
|
apiUrl,
|
|
7619
7666
|
webAppUrl,
|
|
7620
|
-
loginContext
|
|
7667
|
+
loginContext,
|
|
7668
|
+
loginPath
|
|
7621
7669
|
}) {
|
|
7622
7670
|
debug11(
|
|
7623
7671
|
"handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
|
|
@@ -7629,16 +7677,16 @@ async function handleMobbLogin({
|
|
|
7629
7677
|
const { createSpinner } = Spinner({ ci: skipPrompts });
|
|
7630
7678
|
const authManager = new AuthManager(webAppUrl, apiUrl);
|
|
7631
7679
|
authManager.setGQLClient(inGqlClient);
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7680
|
+
const authResult = await authManager.checkAuthentication();
|
|
7681
|
+
if (authResult.isAuthenticated) {
|
|
7682
|
+
createSpinner().start().success({
|
|
7683
|
+
text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
|
|
7684
|
+
});
|
|
7685
|
+
return authManager.getGQLClient();
|
|
7686
|
+
}
|
|
7687
|
+
if (authResult.reason === "unknown") {
|
|
7688
|
+
debug11("Auth check returned unknown: %s", authResult.message);
|
|
7689
|
+
throw new CliError(`Cannot verify authentication: ${authResult.message}`);
|
|
7642
7690
|
}
|
|
7643
7691
|
if (apiKey) {
|
|
7644
7692
|
createSpinner().start().error({
|
|
@@ -7657,7 +7705,7 @@ async function handleMobbLogin({
|
|
|
7657
7705
|
text: "\u{1F513} Waiting for Mobb login..."
|
|
7658
7706
|
});
|
|
7659
7707
|
try {
|
|
7660
|
-
const loginUrl = await authManager.generateLoginUrl(loginContext);
|
|
7708
|
+
const loginUrl = await authManager.generateLoginUrl(loginPath, loginContext);
|
|
7661
7709
|
if (!loginUrl) {
|
|
7662
7710
|
loginSpinner.error({
|
|
7663
7711
|
text: "Failed to generate login URL"
|