mobbdev 1.2.32 → 1.2.34

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
@@ -73,6 +73,12 @@ function getSdk(client, withWrapper = defaultWrapper) {
73
73
  FinalizeAIBlameInferencesUpload(variables, requestHeaders, signal) {
74
74
  return withWrapper((wrappedRequestHeaders) => client.request({ document: FinalizeAiBlameInferencesUploadDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "FinalizeAIBlameInferencesUpload", "mutation", variables);
75
75
  },
76
+ UploadTracyRecords(variables, requestHeaders, signal) {
77
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: UploadTracyRecordsDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "UploadTracyRecords", "mutation", variables);
78
+ },
79
+ GetTracyRawDataUploadUrls(variables, requestHeaders, signal) {
80
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetTracyRawDataUploadUrlsDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetTracyRawDataUploadUrls", "mutation", variables);
81
+ },
76
82
  DigestVulnerabilityReport(variables, requestHeaders, signal) {
77
83
  return withWrapper((wrappedRequestHeaders) => client.request({ document: DigestVulnerabilityReportDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "DigestVulnerabilityReport", "mutation", variables);
78
84
  },
@@ -126,7 +132,7 @@ function getSdk(client, withWrapper = defaultWrapper) {
126
132
  }
127
133
  };
128
134
  }
129
- var AiBlameInferenceType, FixQuestionInputType, Language, ManifestAction, Effort_To_Apply_Fix_Enum, Fix_Rating_Tag_Enum, Fix_Report_State_Enum, Fix_State_Enum, IssueLanguage_Enum, IssueType_Enum, Pr_Status_Enum, Project_Role_Type_Enum, Vulnerability_Report_Issue_Category_Enum, Vulnerability_Report_Issue_State_Enum, Vulnerability_Report_Issue_Tag_Enum, Vulnerability_Report_Vendor_Enum, Vulnerability_Severity_Enum, FixDetailsFragmentDoc, FixReportSummaryFieldsFragmentDoc, MeDocument, GetLastOrgAndNamedProjectDocument, GetLastOrgDocument, GetEncryptedApiTokenDocument, FixReportStateDocument, GetVulnerabilityReportPathsDocument, GetAnalysisSubscriptionDocument, GetAnalysisDocument, GetFixesDocument, GetVulByNodesMetadataDocument, GetFalsePositiveDocument, UpdateScmTokenDocument, UploadS3BucketInfoDocument, GetTracyDiffUploadUrlDocument, AnalyzeCommitForExtensionAiBlameDocument, GetAiBlameInferenceDocument, GetAiBlameAttributionPromptDocument, GetPromptSummaryDocument, UploadAiBlameInferencesInitDocument, FinalizeAiBlameInferencesUploadDocument, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, defaultWrapper;
135
+ var AiBlameInferenceType, FixQuestionInputType, Language, ManifestAction, Effort_To_Apply_Fix_Enum, Fix_Rating_Tag_Enum, Fix_Report_State_Enum, Fix_State_Enum, IssueLanguage_Enum, IssueType_Enum, Pr_Status_Enum, Project_Role_Type_Enum, Vulnerability_Report_Issue_Category_Enum, Vulnerability_Report_Issue_State_Enum, Vulnerability_Report_Issue_Tag_Enum, Vulnerability_Report_Vendor_Enum, Vulnerability_Severity_Enum, FixDetailsFragmentDoc, FixReportSummaryFieldsFragmentDoc, MeDocument, GetLastOrgAndNamedProjectDocument, GetLastOrgDocument, GetEncryptedApiTokenDocument, FixReportStateDocument, GetVulnerabilityReportPathsDocument, GetAnalysisSubscriptionDocument, GetAnalysisDocument, GetFixesDocument, GetVulByNodesMetadataDocument, GetFalsePositiveDocument, UpdateScmTokenDocument, UploadS3BucketInfoDocument, GetTracyDiffUploadUrlDocument, AnalyzeCommitForExtensionAiBlameDocument, GetAiBlameInferenceDocument, GetAiBlameAttributionPromptDocument, GetPromptSummaryDocument, UploadAiBlameInferencesInitDocument, FinalizeAiBlameInferencesUploadDocument, UploadTracyRecordsDocument, GetTracyRawDataUploadUrlsDocument, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, defaultWrapper;
130
136
  var init_client_generates = __esm({
131
137
  "src/features/analysis/scm/generates/client_generates.ts"() {
132
138
  "use strict";
@@ -962,6 +968,28 @@ var init_client_generates = __esm({
962
968
  status
963
969
  error
964
970
  }
971
+ }
972
+ `;
973
+ UploadTracyRecordsDocument = `
974
+ mutation UploadTracyRecords($records: [TracyRecordInput!]!) {
975
+ uploadTracyRecords(records: $records) {
976
+ status
977
+ error
978
+ }
979
+ }
980
+ `;
981
+ GetTracyRawDataUploadUrlsDocument = `
982
+ mutation GetTracyRawDataUploadUrls($recordIds: [String!]!) {
983
+ getTracyRawDataUploadUrls(recordIds: $recordIds) {
984
+ status
985
+ error
986
+ uploads {
987
+ recordId
988
+ url
989
+ uploadFieldsJSON
990
+ uploadKey
991
+ }
992
+ }
965
993
  }
966
994
  `;
967
995
  DigestVulnerabilityReportDocument = `
@@ -4126,7 +4154,7 @@ ${rootContent}`;
4126
4154
  });
4127
4155
 
4128
4156
  // src/index.ts
4129
- import Debug20 from "debug";
4157
+ import Debug22 from "debug";
4130
4158
  import { hideBin } from "yargs/helpers";
4131
4159
 
4132
4160
  // src/args/yargs.ts
@@ -7575,6 +7603,9 @@ var AdoSCMLib = class extends SCMLib {
7575
7603
  async getSubmitRequestMetadata(_submitRequestId) {
7576
7604
  throw new Error("getSubmitRequestMetadata not implemented for ADO");
7577
7605
  }
7606
+ async getPrFiles(_prNumber) {
7607
+ throw new Error("getPrFiles not implemented for ADO");
7608
+ }
7578
7609
  async searchSubmitRequests(_params) {
7579
7610
  throw new Error("searchSubmitRequests not implemented for ADO");
7580
7611
  }
@@ -8154,6 +8185,9 @@ var BitbucketSCMLib = class extends SCMLib {
8154
8185
  async getSubmitRequestMetadata(_submitRequestId) {
8155
8186
  throw new Error("getSubmitRequestMetadata not implemented for Bitbucket");
8156
8187
  }
8188
+ async getPrFiles(_prNumber) {
8189
+ throw new Error("getPrFiles not implemented for Bitbucket");
8190
+ }
8157
8191
  async searchSubmitRequests(_params) {
8158
8192
  throw new Error("searchSubmitRequests not implemented for Bitbucket");
8159
8193
  }
@@ -9355,6 +9389,19 @@ var GithubSCMLib = class extends SCMLib {
9355
9389
  headCommitSha: pr.head.sha
9356
9390
  };
9357
9391
  }
9392
+ async getPrFiles(prNumber) {
9393
+ this._validateAccessTokenAndUrl();
9394
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
9395
+ const filesRes = await this.githubSdk.listPRFiles({
9396
+ owner,
9397
+ repo,
9398
+ pull_number: prNumber
9399
+ });
9400
+ return filesRes.data.filter((file) => {
9401
+ const status = file.status;
9402
+ return status === "added" || status === "modified" || status === "renamed" && file.changes > 0 || status === "copied" && file.changes > 0;
9403
+ }).map((file) => file.filename);
9404
+ }
9358
9405
  /**
9359
9406
  * Override searchSubmitRequests to use GitHub's Search API for efficient pagination.
9360
9407
  * This is much faster than fetching all PRs and filtering in-memory.
@@ -10491,6 +10538,9 @@ var GitlabSCMLib = class extends SCMLib {
10491
10538
  headCommitSha: mr.sha
10492
10539
  };
10493
10540
  }
10541
+ async getPrFiles(_prNumber) {
10542
+ throw new Error("getPrFiles not implemented for GitLab");
10543
+ }
10494
10544
  async searchSubmitRequests(params) {
10495
10545
  this._validateAccessTokenAndUrl();
10496
10546
  const page = parseCursorSafe(params.cursor, 1);
@@ -10752,6 +10802,10 @@ var StubSCMLib = class extends SCMLib {
10752
10802
  async getSubmitRequestMetadata(_submitRequestId) {
10753
10803
  throw new Error("getSubmitRequestMetadata() not implemented");
10754
10804
  }
10805
+ async getPrFiles(_prNumber) {
10806
+ console.warn("getPrFiles() returning empty array");
10807
+ return [];
10808
+ }
10755
10809
  async getPullRequestMetrics(_prNumber) {
10756
10810
  console.warn("getPullRequestMetrics() returning empty object");
10757
10811
  throw new Error("getPullRequestMetrics() not implemented");
@@ -11674,34 +11728,35 @@ var ScanContext = {
11674
11728
 
11675
11729
  // src/args/commands/analyze.ts
11676
11730
  import fs12 from "fs";
11677
- import chalk9 from "chalk";
11731
+ import chalk10 from "chalk";
11678
11732
 
11679
11733
  // src/commands/index.ts
11680
- import chalk7 from "chalk";
11734
+ import chalk8 from "chalk";
11681
11735
  import chalkAnimation from "chalk-animation";
11682
11736
 
11683
11737
  // src/features/analysis/index.ts
11684
11738
  import fs10 from "fs";
11685
- import fsPromises2 from "fs/promises";
11686
- import path9 from "path";
11739
+ import fsPromises3 from "fs/promises";
11740
+ import path10 from "path";
11687
11741
  import { env as env2 } from "process";
11688
11742
  import { pipeline } from "stream/promises";
11689
- import chalk6 from "chalk";
11690
- import Debug19 from "debug";
11743
+ import chalk7 from "chalk";
11744
+ import Debug21 from "debug";
11691
11745
  import extract from "extract-zip";
11692
11746
  import { createSpinner as createSpinner4 } from "nanospinner";
11693
11747
  import fetch4 from "node-fetch";
11694
11748
  import open3 from "open";
11695
11749
  import tmp2 from "tmp";
11696
- import { z as z30 } from "zod";
11750
+ import { z as z31 } from "zod";
11697
11751
 
11698
11752
  // src/commands/handleMobbLogin.ts
11699
- import chalk3 from "chalk";
11700
- import Debug7 from "debug";
11753
+ import chalk4 from "chalk";
11754
+ import Debug10 from "debug";
11701
11755
 
11702
11756
  // src/commands/AuthManager.ts
11703
11757
  import crypto from "crypto";
11704
- import os from "os";
11758
+ import os3 from "os";
11759
+ import Debug9 from "debug";
11705
11760
  import open from "open";
11706
11761
 
11707
11762
  // src/features/analysis/graphql/gql.ts
@@ -11821,8 +11876,7 @@ function getProxyAgent(url) {
11821
11876
  const isHttps = parsedUrl.protocol === "https:";
11822
11877
  const proxy = isHttps ? getHttpProxy() : isHttp ? getHttpProxyOnly() : null;
11823
11878
  if (proxy) {
11824
- debug6("Using proxy %s", proxy);
11825
- debug6("Proxy agent %o", proxy);
11879
+ debug6("Using proxy %s for %s", proxy, url);
11826
11880
  return new HttpsProxyAgent(proxy);
11827
11881
  }
11828
11882
  } catch (err) {
@@ -12474,6 +12528,12 @@ var GQLClient = class {
12474
12528
  async finalizeAIBlameInferencesUploadRaw(variables) {
12475
12529
  return await this._clientSdk.FinalizeAIBlameInferencesUpload(variables);
12476
12530
  }
12531
+ async uploadTracyRecords(variables) {
12532
+ return await this._clientSdk.UploadTracyRecords(variables);
12533
+ }
12534
+ async getTracyRawDataUploadUrls(variables) {
12535
+ return await this._clientSdk.GetTracyRawDataUploadUrls(variables);
12536
+ }
12477
12537
  async analyzeCommitForExtensionAIBlame(variables) {
12478
12538
  return await this._clientSdk.AnalyzeCommitForExtensionAIBlame(variables);
12479
12539
  }
@@ -12491,38 +12551,76 @@ var GQLClient = class {
12491
12551
  }
12492
12552
  };
12493
12553
 
12494
- // src/mcp/services/types.ts
12495
- function detectIDE() {
12496
- const env3 = process.env;
12497
- if (env3["CURSOR_TRACE_ID"] || env3["CURSOR_SESSION_ID"]) return "cursor";
12498
- if (env3["WINDSURF_IPC_HOOK"] || env3["WINDSURF_PID"]) return "windsurf";
12499
- if (env3["CLAUDE_DESKTOP"] || env3["ANTHROPIC_CLAUDE"]) return "claude";
12500
- if (env3["WEBSTORM_VM_OPTIONS"] || env3["IDEA_VM_OPTIONS"] || env3["JETBRAINS_IDE"])
12501
- return "webstorm";
12502
- if (env3["VSCODE_IPC_HOOK"] || env3["VSCODE_PID"]) return "vscode";
12503
- const termProgram = env3["TERM_PROGRAM"]?.toLowerCase();
12504
- if (termProgram === "windsurf") return "windsurf";
12505
- if (termProgram === "vscode") return "vscode";
12506
- return void 0;
12507
- }
12508
- function createMcpLoginContext(trigger) {
12509
- return {
12510
- trigger,
12511
- source: "mcp",
12512
- ide: detectIDE()
12513
- };
12514
- }
12515
- function buildLoginUrl(baseUrl, loginId, hostname, context) {
12516
- const url = new URL(`${baseUrl}/${loginId}`);
12517
- url.searchParams.set("hostname", hostname);
12518
- url.searchParams.set("trigger", context.trigger);
12519
- url.searchParams.set("source", context.source);
12520
- if (context.ide) {
12521
- url.searchParams.set("ide", context.ide);
12554
+ // src/features/analysis/graphql/tracy-batch-upload.ts
12555
+ import { promisify } from "util";
12556
+ import { gzip } from "zlib";
12557
+ import Debug8 from "debug";
12558
+
12559
+ // src/args/commands/upload_ai_blame.ts
12560
+ import fsPromises2 from "fs/promises";
12561
+ import * as os2 from "os";
12562
+ import path7 from "path";
12563
+ import chalk3 from "chalk";
12564
+ import { withFile } from "tmp-promise";
12565
+ import z27 from "zod";
12566
+ init_client_generates();
12567
+ init_GitService();
12568
+ init_urlParser2();
12569
+
12570
+ // src/features/analysis/upload-file.ts
12571
+ import Debug7 from "debug";
12572
+ import fetch3, { File, fileFrom, FormData } from "node-fetch";
12573
+ var debug8 = Debug7("mobbdev:upload-file");
12574
+ async function uploadFile({
12575
+ file,
12576
+ url,
12577
+ uploadKey,
12578
+ uploadFields,
12579
+ logger: logger2
12580
+ }) {
12581
+ const logInfo2 = logger2 || ((_message, _data) => {
12582
+ });
12583
+ logInfo2(`FileUpload: upload file start ${url}`);
12584
+ logInfo2(`FileUpload: upload fields`, uploadFields);
12585
+ logInfo2(`FileUpload: upload key ${uploadKey}`);
12586
+ debug8("upload file start %s", url);
12587
+ debug8("upload fields %o", uploadFields);
12588
+ debug8("upload key %s", uploadKey);
12589
+ const form = new FormData();
12590
+ Object.entries(uploadFields).forEach(([key, value]) => {
12591
+ form.append(key, value);
12592
+ });
12593
+ if (!form.has("key")) {
12594
+ form.append("key", uploadKey);
12522
12595
  }
12523
- return url.toString();
12596
+ if (typeof file === "string") {
12597
+ debug8("upload file from path %s", file);
12598
+ logInfo2(`FileUpload: upload file from path ${file}`);
12599
+ form.append("file", await fileFrom(file));
12600
+ } else {
12601
+ debug8("upload file from buffer");
12602
+ logInfo2(`FileUpload: upload file from buffer`);
12603
+ form.append("file", new File([new Uint8Array(file)], "file"));
12604
+ }
12605
+ const agent = getProxyAgent(url);
12606
+ const response = await fetch3(url, {
12607
+ method: "POST",
12608
+ body: form,
12609
+ agent
12610
+ });
12611
+ if (!response.ok) {
12612
+ debug8("error from S3 %s %s", response.body, response.status);
12613
+ logInfo2(`FileUpload: error from S3 ${response.body} ${response.status}`);
12614
+ throw new Error(`Failed to upload the file: ${response.status}`);
12615
+ }
12616
+ debug8("upload file done");
12617
+ logInfo2(`FileUpload: upload file done`);
12524
12618
  }
12525
12619
 
12620
+ // src/utils/computerName.ts
12621
+ import { execSync } from "child_process";
12622
+ import os from "os";
12623
+
12526
12624
  // src/utils/ConfigStoreService.ts
12527
12625
  import Configstore from "configstore";
12528
12626
  function createConfigStore(defaultValues = { apiToken: "" }) {
@@ -12542,340 +12640,942 @@ function getConfigStore() {
12542
12640
  }
12543
12641
  var configStore = getConfigStore();
12544
12642
 
12545
- // src/commands/AuthManager.ts
12546
- var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
12547
- var LOGIN_CHECK_DELAY = 5 * 1e3;
12548
- var AuthManager = class {
12549
- constructor(webAppUrl, apiUrl) {
12550
- __publicField(this, "publicKey");
12551
- __publicField(this, "privateKey");
12552
- __publicField(this, "loginId");
12553
- __publicField(this, "gqlClient");
12554
- __publicField(this, "currentBrowserUrl");
12555
- __publicField(this, "authenticated", null);
12556
- __publicField(this, "resolvedWebAppUrl");
12557
- __publicField(this, "resolvedApiUrl");
12558
- this.resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
12559
- this.resolvedApiUrl = apiUrl || API_URL;
12560
- }
12561
- openUrlInBrowser() {
12562
- if (this.currentBrowserUrl) {
12563
- open(this.currentBrowserUrl);
12564
- return true;
12565
- }
12566
- return false;
12567
- }
12568
- async waitForAuthentication() {
12569
- let newApiToken = null;
12570
- for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
12571
- newApiToken = await this.getApiToken();
12572
- if (newApiToken) {
12573
- break;
12643
+ // src/utils/computerName.ts
12644
+ var STABLE_COMPUTER_NAME_CONFIG_KEY = "stableComputerName";
12645
+ var HOSTNAME_SUFFIXES = [
12646
+ // Cloud providers (must be first - most specific)
12647
+ ".ec2.internal",
12648
+ ".compute.internal",
12649
+ ".cloudapp.net",
12650
+ // mDNS/Bonjour
12651
+ ".local",
12652
+ ".localhost",
12653
+ ".localdomain",
12654
+ // Home networks
12655
+ ".lan",
12656
+ ".home",
12657
+ ".homelan",
12658
+ // Corporate networks
12659
+ ".corp",
12660
+ ".internal",
12661
+ ".intranet",
12662
+ ".domain",
12663
+ ".work",
12664
+ ".office",
12665
+ // Container environments
12666
+ ".docker",
12667
+ ".kubernetes",
12668
+ ".k8s"
12669
+ ];
12670
+ function getOsSpecificComputerName() {
12671
+ const platform2 = os.platform();
12672
+ try {
12673
+ if (platform2 === "darwin") {
12674
+ const result = execSync("scutil --get LocalHostName", {
12675
+ encoding: "utf-8",
12676
+ timeout: 1e3
12677
+ });
12678
+ return result.trim();
12679
+ } else if (platform2 === "win32") {
12680
+ return process.env["COMPUTERNAME"] || null;
12681
+ } else {
12682
+ try {
12683
+ const result2 = execSync("hostnamectl --static", {
12684
+ encoding: "utf-8",
12685
+ timeout: 1e3
12686
+ });
12687
+ const hostname = result2.trim();
12688
+ if (hostname) return hostname;
12689
+ } catch {
12574
12690
  }
12575
- await sleep(LOGIN_CHECK_DELAY);
12576
- }
12577
- if (!newApiToken) {
12578
- return false;
12579
- }
12580
- this.gqlClient = new GQLClient({
12581
- apiKey: newApiToken,
12582
- type: "apiKey",
12583
- apiUrl: this.resolvedApiUrl
12584
- });
12585
- const loginSuccess = await this.gqlClient.validateUserToken();
12586
- if (loginSuccess) {
12587
- configStore.set("apiToken", newApiToken);
12588
- this.authenticated = true;
12589
- return true;
12691
+ const result = execSync("cat /etc/hostname", {
12692
+ encoding: "utf-8",
12693
+ timeout: 1e3
12694
+ });
12695
+ return result.trim();
12590
12696
  }
12591
- return false;
12697
+ } catch (error) {
12698
+ return null;
12592
12699
  }
12593
- /**
12594
- * Checks if the user is already authenticated
12595
- */
12596
- async isAuthenticated() {
12597
- if (this.authenticated === null) {
12598
- const result = await this.checkAuthentication();
12599
- this.authenticated = result.isAuthenticated;
12700
+ }
12701
+ function stripHostnameSuffixes(hostname) {
12702
+ if (!hostname) return hostname;
12703
+ let normalized = hostname;
12704
+ for (const suffix of HOSTNAME_SUFFIXES) {
12705
+ if (normalized.endsWith(suffix)) {
12706
+ normalized = normalized.slice(0, -suffix.length);
12707
+ break;
12600
12708
  }
12601
- return this.authenticated;
12602
12709
  }
12603
- /**
12604
- * Private function to check if the user is authenticated with the server
12605
- */
12606
- async checkAuthentication(apiKey) {
12607
- try {
12608
- if (!this.gqlClient) {
12609
- this.gqlClient = this.getGQLClient(apiKey);
12610
- }
12611
- const isConnected = await this.gqlClient.verifyApiConnection();
12612
- if (!isConnected) {
12613
- return {
12614
- isAuthenticated: false,
12615
- message: "Failed to connect to Mobb server"
12616
- };
12617
- }
12618
- const userVerify = await this.gqlClient.validateUserToken();
12619
- if (!userVerify) {
12620
- return {
12621
- isAuthenticated: false,
12622
- message: "User token validation failed"
12623
- };
12710
+ return normalized;
12711
+ }
12712
+ function generateStableComputerName() {
12713
+ const osSpecificName = getOsSpecificComputerName();
12714
+ if (osSpecificName) {
12715
+ return osSpecificName;
12716
+ }
12717
+ const currentHostname = os.hostname();
12718
+ return stripHostnameSuffixes(currentHostname);
12719
+ }
12720
+ function getStableComputerName() {
12721
+ const cached = configStore.get(STABLE_COMPUTER_NAME_CONFIG_KEY);
12722
+ if (cached) {
12723
+ return cached;
12724
+ }
12725
+ const currentName = generateStableComputerName();
12726
+ configStore.set(STABLE_COMPUTER_NAME_CONFIG_KEY, currentName);
12727
+ return currentName;
12728
+ }
12729
+
12730
+ // src/utils/sanitize-sensitive-data.ts
12731
+ import { OpenRedaction } from "@openredaction/openredaction";
12732
+ var openRedaction = new OpenRedaction({
12733
+ patterns: [
12734
+ // Core Personal Data
12735
+ // Removed EMAIL - causes false positives in code/test snippets (e.g. --author="Eve Author <eve@example.com>")
12736
+ // Prefer false negatives over false positives for this use case.
12737
+ "SSN",
12738
+ "NATIONAL_INSURANCE_UK",
12739
+ "DATE_OF_BIRTH",
12740
+ // Identity Documents
12741
+ "PASSPORT_UK",
12742
+ "PASSPORT_US",
12743
+ "PASSPORT_MRZ_TD1",
12744
+ "PASSPORT_MRZ_TD3",
12745
+ "DRIVING_LICENSE_UK",
12746
+ "DRIVING_LICENSE_US",
12747
+ "VISA_NUMBER",
12748
+ "VISA_MRZ",
12749
+ "TAX_ID",
12750
+ // Financial Data (removed SWIFT_BIC, CARD_AUTH_CODE - too broad, causing false positives with authentication words)
12751
+ // Removed CREDIT_CARD - causes false positives on zero-filled UUIDs (e.g. '00000000-0000-0000-0000-000000000000')
12752
+ // Prefer false negatives over false positives for this use case.
12753
+ "IBAN",
12754
+ "BANK_ACCOUNT_UK",
12755
+ "ROUTING_NUMBER_US",
12756
+ "CARD_TRACK1_DATA",
12757
+ "CARD_TRACK2_DATA",
12758
+ "CARD_EXPIRY",
12759
+ // Cryptocurrency (removed BITCOIN_ADDRESS - too broad, matches hash-like strings)
12760
+ "ETHEREUM_ADDRESS",
12761
+ "LITECOIN_ADDRESS",
12762
+ "CARDANO_ADDRESS",
12763
+ "SOLANA_ADDRESS",
12764
+ "MONERO_ADDRESS",
12765
+ "RIPPLE_ADDRESS",
12766
+ // Medical Data (removed PRESCRIPTION_NUMBER - too broad, matches words containing "ription")
12767
+ // Removed MEDICAL_RECORD_NUMBER - too broad, "MR" prefix matches "Merge Request" in SCM contexts (e.g. "MR branches" → "MR br****es")
12768
+ "NHS_NUMBER",
12769
+ "AUSTRALIAN_MEDICARE",
12770
+ "HEALTH_PLAN_NUMBER",
12771
+ "PATIENT_ID",
12772
+ // Communications (removed EMERGENCY_CONTACT, ADDRESS_PO_BOX, ZIP_CODE_US, PHONE_US, PHONE_INTERNATIONAL - too broad, causing false positives)
12773
+ "PHONE_UK",
12774
+ "PHONE_UK_MOBILE",
12775
+ "PHONE_LINE_NUMBER",
12776
+ "ADDRESS_STREET",
12777
+ "POSTCODE_UK",
12778
+ // Network & Technical
12779
+ "IPV4",
12780
+ "IPV6",
12781
+ "MAC_ADDRESS",
12782
+ "URL_WITH_AUTH",
12783
+ // Security Keys & Tokens
12784
+ "PRIVATE_KEY",
12785
+ "SSH_PRIVATE_KEY",
12786
+ "AWS_SECRET_KEY",
12787
+ "AWS_ACCESS_KEY",
12788
+ "AZURE_STORAGE_KEY",
12789
+ "GCP_SERVICE_ACCOUNT",
12790
+ "JWT_TOKEN",
12791
+ "OAUTH_TOKEN",
12792
+ "OAUTH_CLIENT_SECRET",
12793
+ "BEARER_TOKEN",
12794
+ "PAYMENT_TOKEN",
12795
+ "GENERIC_SECRET",
12796
+ "GENERIC_API_KEY",
12797
+ // Platform-Specific API Keys
12798
+ "GITHUB_TOKEN",
12799
+ "SLACK_TOKEN",
12800
+ "STRIPE_API_KEY",
12801
+ "GOOGLE_API_KEY",
12802
+ "FIREBASE_API_KEY",
12803
+ "HEROKU_API_KEY",
12804
+ "MAILGUN_API_KEY",
12805
+ "SENDGRID_API_KEY",
12806
+ "TWILIO_API_KEY",
12807
+ "NPM_TOKEN",
12808
+ "PYPI_TOKEN",
12809
+ "DOCKER_AUTH",
12810
+ "KUBERNETES_SECRET",
12811
+ // Government & Legal
12812
+ // Removed CLIENT_ID - too broad, "client" is ubiquitous in code (npm packages like @scope/client-*, class names like ClientSdkOptions)
12813
+ "POLICE_REPORT_NUMBER",
12814
+ "IMMIGRATION_NUMBER",
12815
+ "COURT_REPORTER_LICENSE"
12816
+ ]
12817
+ });
12818
+ function maskString(str, showStart = 2, showEnd = 2) {
12819
+ if (str.length <= showStart + showEnd) {
12820
+ return "*".repeat(str.length);
12821
+ }
12822
+ return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
12823
+ }
12824
+ async function sanitizeDataWithCounts(obj) {
12825
+ const counts = {
12826
+ detections: { total: 0, high: 0, medium: 0, low: 0 }
12827
+ };
12828
+ const sanitizeString = async (str) => {
12829
+ let result = str;
12830
+ const piiDetections = openRedaction.scan(str);
12831
+ if (piiDetections && piiDetections.total > 0) {
12832
+ const allDetections = [
12833
+ ...piiDetections.high,
12834
+ ...piiDetections.medium,
12835
+ ...piiDetections.low
12836
+ ];
12837
+ for (const detection of allDetections) {
12838
+ counts.detections.total++;
12839
+ if (detection.severity === "high") counts.detections.high++;
12840
+ else if (detection.severity === "medium") counts.detections.medium++;
12841
+ else if (detection.severity === "low") counts.detections.low++;
12842
+ const masked = maskString(detection.value);
12843
+ result = result.replaceAll(detection.value, masked);
12624
12844
  }
12625
- } catch (error) {
12626
- return {
12627
- isAuthenticated: false,
12628
- message: error instanceof Error ? error.message : "Unknown authentication error"
12629
- };
12630
12845
  }
12631
- return { isAuthenticated: true, message: "Successfully authenticated" };
12632
- }
12633
- /**
12634
- * Generates a login URL for manual authentication
12635
- */
12636
- async generateLoginUrl(loginContext) {
12637
- try {
12638
- if (!this.gqlClient) {
12639
- this.gqlClient = this.getGQLClient();
12846
+ return result;
12847
+ };
12848
+ const sanitizeRecursive = async (data) => {
12849
+ if (typeof data === "string") {
12850
+ return sanitizeString(data);
12851
+ } else if (Array.isArray(data)) {
12852
+ return Promise.all(data.map((item) => sanitizeRecursive(item)));
12853
+ } else if (data instanceof Error) {
12854
+ return data;
12855
+ } else if (data instanceof Date) {
12856
+ return data;
12857
+ } else if (typeof data === "object" && data !== null) {
12858
+ const sanitized = {};
12859
+ const record = data;
12860
+ for (const key in record) {
12861
+ if (Object.prototype.hasOwnProperty.call(record, key)) {
12862
+ sanitized[key] = await sanitizeRecursive(record[key]);
12863
+ }
12640
12864
  }
12641
- const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
12642
- modulusLength: 2048
12643
- });
12644
- this.publicKey = publicKey;
12645
- this.privateKey = privateKey;
12646
- this.loginId = await this.gqlClient.createCliLogin({
12647
- publicKey: this.publicKey.export({ format: "pem", type: "pkcs1" }).toString()
12648
- });
12649
- const webLoginUrl = `${this.resolvedWebAppUrl}/cli-login`;
12650
- const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, this.loginId, os.hostname(), loginContext) : `${webLoginUrl}/${this.loginId}?hostname=${os.hostname()}`;
12651
- this.currentBrowserUrl = browserUrl;
12652
- return browserUrl;
12653
- } catch (error) {
12654
- console.error("Failed to generate login URL:", error);
12655
- return null;
12865
+ return sanitized;
12866
+ }
12867
+ return data;
12868
+ };
12869
+ const sanitizedData = await sanitizeRecursive(obj);
12870
+ return { sanitizedData, counts };
12871
+ }
12872
+
12873
+ // src/args/commands/upload_ai_blame.ts
12874
+ var defaultLogger2 = {
12875
+ info: (msg, data) => {
12876
+ if (data !== void 0) {
12877
+ console.log(msg, data);
12878
+ } else {
12879
+ console.log(msg);
12880
+ }
12881
+ },
12882
+ error: (msg, data) => {
12883
+ if (data !== void 0) {
12884
+ console.error(msg, data);
12885
+ } else {
12886
+ console.error(msg);
12656
12887
  }
12657
12888
  }
12658
- /**
12659
- * Retrieves and decrypts the API token after authentication
12660
- */
12661
- async getApiToken() {
12662
- if (!this.gqlClient || !this.loginId || !this.privateKey) {
12889
+ };
12890
+ var PromptItemZ = z27.object({
12891
+ type: z27.enum([
12892
+ "USER_PROMPT",
12893
+ "AI_RESPONSE",
12894
+ "TOOL_EXECUTION",
12895
+ "AI_THINKING",
12896
+ "MCP_TOOL_CALL"
12897
+ // MCP (Model Context Protocol) tool invocation
12898
+ ]),
12899
+ attachedFiles: z27.array(
12900
+ z27.object({
12901
+ relativePath: z27.string(),
12902
+ startLine: z27.number().optional()
12903
+ })
12904
+ ).optional(),
12905
+ tokens: z27.object({
12906
+ inputCount: z27.number(),
12907
+ outputCount: z27.number()
12908
+ }).optional(),
12909
+ text: z27.string().optional(),
12910
+ date: z27.date().optional(),
12911
+ tool: z27.object({
12912
+ name: z27.string(),
12913
+ parameters: z27.string(),
12914
+ result: z27.string(),
12915
+ rawArguments: z27.string().optional(),
12916
+ accepted: z27.boolean().optional(),
12917
+ // MCP-specific fields (only populated for MCP_TOOL_CALL type)
12918
+ mcpServer: z27.string().optional(),
12919
+ // MCP server name (e.g., "datadog", "mobb-mcp")
12920
+ mcpToolName: z27.string().optional()
12921
+ // MCP tool name without prefix (e.g., "scan_and_fix_vulnerabilities")
12922
+ }).optional()
12923
+ });
12924
+ var PromptItemArrayZ = z27.array(PromptItemZ);
12925
+ async function getRepositoryUrl() {
12926
+ try {
12927
+ const gitService = new GitService(process.cwd());
12928
+ const isRepo = await gitService.isGitRepository();
12929
+ if (!isRepo) {
12663
12930
  return null;
12664
12931
  }
12665
- const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
12666
- loginId: this.loginId
12667
- });
12668
- if (encryptedApiToken) {
12669
- return crypto.privateDecrypt(
12670
- this.privateKey,
12671
- Buffer.from(encryptedApiToken, "base64")
12672
- ).toString("utf-8");
12673
- }
12932
+ const remoteUrl = await gitService.getRemoteUrl();
12933
+ const parsed = parseScmURL(remoteUrl);
12934
+ return parsed?.scmType === "GitHub" /* GitHub */ || parsed?.scmType === "GitLab" /* GitLab */ ? remoteUrl : null;
12935
+ } catch {
12674
12936
  return null;
12675
12937
  }
12676
- /**
12677
- * Gets the current GQL client (if authenticated)
12678
- */
12679
- getGQLClient(inputApiKey) {
12680
- if (this.gqlClient === void 0) {
12681
- this.gqlClient = new GQLClient({
12682
- apiKey: inputApiKey || configStore.get("apiToken") || "",
12683
- type: "apiKey",
12684
- apiUrl: this.resolvedApiUrl
12938
+ }
12939
+ function getSystemInfo() {
12940
+ let userName;
12941
+ try {
12942
+ userName = os2.userInfo().username;
12943
+ } catch {
12944
+ userName = void 0;
12945
+ }
12946
+ return {
12947
+ computerName: getStableComputerName(),
12948
+ userName
12949
+ };
12950
+ }
12951
+ function uploadAiBlameBuilder(args) {
12952
+ return args.option("prompt", {
12953
+ type: "string",
12954
+ array: true,
12955
+ demandOption: true,
12956
+ describe: chalk3.bold("Path(s) to prompt artifact(s) (one per session)")
12957
+ }).option("inference", {
12958
+ type: "string",
12959
+ array: true,
12960
+ demandOption: true,
12961
+ describe: chalk3.bold(
12962
+ "Path(s) to inference artifact(s) (one per session)"
12963
+ )
12964
+ }).option("ai-response-at", {
12965
+ type: "string",
12966
+ array: true,
12967
+ describe: chalk3.bold(
12968
+ "ISO timestamp(s) for AI response (one per session, defaults to now)"
12969
+ )
12970
+ }).option("model", {
12971
+ type: "string",
12972
+ array: true,
12973
+ describe: chalk3.bold("AI model name(s) (optional, one per session)")
12974
+ }).option("tool-name", {
12975
+ type: "string",
12976
+ array: true,
12977
+ describe: chalk3.bold("Tool/IDE name(s) (optional, one per session)")
12978
+ }).option("blame-type", {
12979
+ type: "string",
12980
+ array: true,
12981
+ choices: Object.values(AiBlameInferenceType),
12982
+ describe: chalk3.bold(
12983
+ "Blame type(s) (optional, one per session, defaults to CHAT)"
12984
+ )
12985
+ }).strict();
12986
+ }
12987
+ async function uploadAiBlameHandlerFromExtension(args) {
12988
+ const uploadArgs = {
12989
+ prompt: [],
12990
+ inference: [],
12991
+ model: [],
12992
+ toolName: [],
12993
+ aiResponseAt: [],
12994
+ blameType: [],
12995
+ sessionId: []
12996
+ };
12997
+ let promptsCounts;
12998
+ let inferenceCounts;
12999
+ let promptsUUID;
13000
+ let inferenceUUID;
13001
+ await withFile(async (promptFile) => {
13002
+ const promptsResult = await sanitizeDataWithCounts(args.prompts);
13003
+ promptsCounts = promptsResult.counts;
13004
+ promptsUUID = path7.basename(promptFile.path, path7.extname(promptFile.path));
13005
+ await fsPromises2.writeFile(
13006
+ promptFile.path,
13007
+ JSON.stringify(promptsResult.sanitizedData, null, 2),
13008
+ "utf-8"
13009
+ );
13010
+ uploadArgs.prompt.push(promptFile.path);
13011
+ await withFile(async (inferenceFile) => {
13012
+ const inferenceResult = await sanitizeDataWithCounts(args.inference);
13013
+ inferenceCounts = inferenceResult.counts;
13014
+ inferenceUUID = path7.basename(
13015
+ inferenceFile.path,
13016
+ path7.extname(inferenceFile.path)
13017
+ );
13018
+ await fsPromises2.writeFile(
13019
+ inferenceFile.path,
13020
+ inferenceResult.sanitizedData,
13021
+ "utf-8"
13022
+ );
13023
+ uploadArgs.inference.push(inferenceFile.path);
13024
+ uploadArgs.model.push(args.model);
13025
+ uploadArgs.toolName.push(args.tool);
13026
+ uploadArgs.aiResponseAt.push(args.responseTime);
13027
+ uploadArgs.blameType.push(args.blameType || "CHAT" /* Chat */);
13028
+ if (args.sessionId) {
13029
+ uploadArgs.sessionId.push(args.sessionId);
13030
+ }
13031
+ await uploadAiBlameHandler({
13032
+ args: uploadArgs,
13033
+ exitOnError: false,
13034
+ apiUrl: args.apiUrl,
13035
+ webAppUrl: args.webAppUrl,
13036
+ repositoryUrl: args.repositoryUrl
12685
13037
  });
13038
+ });
13039
+ });
13040
+ return {
13041
+ promptsCounts,
13042
+ inferenceCounts,
13043
+ promptsUUID,
13044
+ inferenceUUID
13045
+ };
13046
+ }
13047
+ async function uploadAiBlameHandler(options) {
13048
+ const {
13049
+ args,
13050
+ exitOnError = true,
13051
+ apiUrl,
13052
+ webAppUrl,
13053
+ logger: logger2 = defaultLogger2
13054
+ } = options;
13055
+ const prompts = args.prompt || [];
13056
+ const inferences = args.inference || [];
13057
+ const models = args.model || [];
13058
+ const tools = args.toolName || args["tool-name"] || [];
13059
+ const responseTimes = args.aiResponseAt || args["ai-response-at"] || [];
13060
+ const blameTypes = args.blameType || args["blame-type"] || [];
13061
+ const sessionIds = args.sessionId || args["session-id"] || [];
13062
+ if (prompts.length !== inferences.length) {
13063
+ const errorMsg = "prompt and inference must have the same number of entries";
13064
+ logger2.error(chalk3.red(errorMsg));
13065
+ if (exitOnError) {
13066
+ process.exit(1);
12686
13067
  }
12687
- return this.gqlClient;
12688
- }
12689
- /**
12690
- * Assigns a GQL client instance to the AuthManager, and resets auth state
12691
- * @param gqlClient The GQL client instance to set
12692
- */
12693
- setGQLClient(gqlClient) {
12694
- this.gqlClient = gqlClient;
12695
- this.cleanup();
13068
+ throw new Error(errorMsg);
12696
13069
  }
12697
- /**
12698
- * Cleans up any active login session
12699
- */
12700
- cleanup() {
12701
- this.publicKey = void 0;
12702
- this.privateKey = void 0;
12703
- this.loginId = void 0;
12704
- this.authenticated = null;
12705
- this.currentBrowserUrl = null;
13070
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
13071
+ const { computerName, userName } = getSystemInfo();
13072
+ const repositoryUrl = options.repositoryUrl !== void 0 ? options.repositoryUrl : await getRepositoryUrl();
13073
+ const sessions = [];
13074
+ for (let i = 0; i < prompts.length; i++) {
13075
+ const promptPath = String(prompts[i]);
13076
+ const inferencePath = String(inferences[i]);
13077
+ try {
13078
+ await Promise.all([
13079
+ fsPromises2.access(promptPath),
13080
+ fsPromises2.access(inferencePath)
13081
+ ]);
13082
+ } catch {
13083
+ const errorMsg = `File not found for session ${i + 1}`;
13084
+ logger2.error(chalk3.red(errorMsg));
13085
+ if (exitOnError) {
13086
+ process.exit(1);
13087
+ }
13088
+ throw new Error(errorMsg);
13089
+ }
13090
+ sessions.push({
13091
+ promptFileName: path7.basename(promptPath),
13092
+ inferenceFileName: path7.basename(inferencePath),
13093
+ aiResponseAt: responseTimes[i] || nowIso,
13094
+ model: models[i],
13095
+ toolName: tools[i],
13096
+ blameType: blameTypes[i] || "CHAT" /* Chat */,
13097
+ computerName,
13098
+ userName,
13099
+ sessionId: sessionIds[i],
13100
+ repositoryUrl
13101
+ });
12706
13102
  }
12707
- };
12708
-
12709
- // src/commands/handleMobbLogin.ts
12710
- var debug8 = Debug7("mobbdev:commands");
12711
- var LOGIN_MAX_WAIT2 = 10 * 60 * 1e3;
12712
- var LOGIN_CHECK_DELAY2 = 5 * 1e3;
12713
- 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(
12714
- "press any key to continue"
12715
- )};`;
12716
- async function getAuthenticatedGQLClient({
12717
- inputApiKey = "",
12718
- isSkipPrompts = true,
12719
- apiUrl,
12720
- webAppUrl
12721
- }) {
12722
- debug8(
12723
- "getAuthenticatedGQLClient called with: apiUrl=%s, webAppUrl=%s",
12724
- apiUrl || "undefined",
12725
- webAppUrl || "undefined"
12726
- );
12727
- const authManager = new AuthManager(webAppUrl, apiUrl);
12728
- let gqlClient = authManager.getGQLClient(inputApiKey);
12729
- gqlClient = await handleMobbLogin({
12730
- inGqlClient: gqlClient,
12731
- skipPrompts: isSkipPrompts,
13103
+ const authenticatedClient = await getAuthenticatedGQLClient({
13104
+ isSkipPrompts: true,
12732
13105
  apiUrl,
12733
13106
  webAppUrl
12734
13107
  });
12735
- return gqlClient;
12736
- }
12737
- async function handleMobbLogin({
12738
- inGqlClient,
12739
- apiKey,
12740
- skipPrompts,
12741
- apiUrl,
12742
- webAppUrl,
12743
- loginContext
12744
- }) {
12745
- debug8(
12746
- "handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
12747
- apiUrl || "fallback",
12748
- apiUrl || "fallback",
12749
- webAppUrl || "fallback",
12750
- webAppUrl || "fallback"
13108
+ const initSessions = sessions.map(
13109
+ ({ sessionId: _sessionId, ...rest }) => rest
12751
13110
  );
12752
- const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
12753
- const authManager = new AuthManager(webAppUrl, apiUrl);
12754
- authManager.setGQLClient(inGqlClient);
12755
- try {
12756
- const isAuthenticated = await authManager.isAuthenticated();
12757
- if (isAuthenticated) {
12758
- createSpinner5().start().success({
12759
- text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
12760
- });
12761
- return authManager.getGQLClient();
13111
+ const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
13112
+ sessions: initSessions
13113
+ });
13114
+ const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
13115
+ if (uploadSessions.length !== sessions.length) {
13116
+ const errorMsg = "Init failed to return expected number of sessions";
13117
+ logger2.error(chalk3.red(errorMsg));
13118
+ if (exitOnError) {
13119
+ process.exit(1);
12762
13120
  }
12763
- } catch (error) {
12764
- debug8("Authentication check failed:", error);
13121
+ throw new Error(errorMsg);
12765
13122
  }
12766
- if (apiKey) {
12767
- createSpinner5().start().error({
12768
- text: "\u{1F513} Login to Mobb failed: The provided API key does not match any configured API key on the system"
12769
- });
12770
- throw new CliError(
12771
- "Login to Mobb failed: The provided API key does not match any configured API key on the system"
12772
- );
13123
+ for (let i = 0; i < uploadSessions.length; i++) {
13124
+ const us = uploadSessions[i];
13125
+ const promptPath = String(prompts[i]);
13126
+ const inferencePath = String(inferences[i]);
13127
+ await Promise.all([
13128
+ // Prompt
13129
+ uploadFile({
13130
+ file: promptPath,
13131
+ url: us.prompt.url,
13132
+ uploadFields: JSON.parse(us.prompt.uploadFieldsJSON),
13133
+ uploadKey: us.prompt.uploadKey
13134
+ }),
13135
+ // Inference
13136
+ uploadFile({
13137
+ file: inferencePath,
13138
+ url: us.inference.url,
13139
+ uploadFields: JSON.parse(us.inference.uploadFieldsJSON),
13140
+ uploadKey: us.inference.uploadKey
13141
+ })
13142
+ ]);
12773
13143
  }
12774
- const loginSpinner = createSpinner5().start();
12775
- if (!skipPrompts) {
12776
- loginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
12777
- await keypress();
12778
- }
12779
- loginSpinner.update({
12780
- text: "\u{1F513} Waiting for Mobb login..."
13144
+ const finalizeSessions = uploadSessions.map((us, i) => {
13145
+ const s = sessions[i];
13146
+ return {
13147
+ aiBlameInferenceId: us.aiBlameInferenceId,
13148
+ promptKey: us.prompt.uploadKey,
13149
+ inferenceKey: us.inference.uploadKey,
13150
+ aiResponseAt: s.aiResponseAt,
13151
+ model: s.model,
13152
+ toolName: s.toolName,
13153
+ blameType: s.blameType,
13154
+ computerName: s.computerName,
13155
+ userName: s.userName,
13156
+ sessionId: s.sessionId,
13157
+ repositoryUrl: s.repositoryUrl
13158
+ };
12781
13159
  });
12782
13160
  try {
12783
- const loginUrl = await authManager.generateLoginUrl(loginContext);
12784
- if (!loginUrl) {
12785
- loginSpinner.error({
12786
- text: "Failed to generate login URL"
12787
- });
12788
- throw new CliError("Failed to generate login URL");
12789
- }
12790
- !skipPrompts && console.log(
12791
- `If the page does not open automatically, kindly access it through ${loginUrl}.`
13161
+ logger2.info(
13162
+ `[UPLOAD] Calling finalizeAIBlameInferencesUploadRaw with ${finalizeSessions.length} sessions`
12792
13163
  );
12793
- authManager.openUrlInBrowser();
12794
- const authSuccess = await authManager.waitForAuthentication();
12795
- if (!authSuccess) {
12796
- loginSpinner.error({
12797
- text: "Login timeout error"
12798
- });
12799
- throw new CliError("Login timeout error");
13164
+ const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw(
13165
+ {
13166
+ sessions: finalizeSessions
13167
+ }
13168
+ );
13169
+ logger2.info("[UPLOAD] Finalize response:", JSON.stringify(finRes, null, 2));
13170
+ const status = finRes?.finalizeAIBlameInferencesUpload?.status;
13171
+ if (status !== "OK") {
13172
+ const errorMsg = finRes?.finalizeAIBlameInferencesUpload?.error || "unknown error";
13173
+ logger2.error(
13174
+ chalk3.red(
13175
+ `[UPLOAD] Finalize failed with status: ${status}, error: ${errorMsg}`
13176
+ )
13177
+ );
13178
+ if (exitOnError) {
13179
+ process.exit(1);
13180
+ }
13181
+ throw new Error(errorMsg);
12800
13182
  }
12801
- loginSpinner.success({
12802
- text: `\u{1F513} Login to Mobb successful!`
12803
- });
12804
- return authManager.getGQLClient();
12805
- } finally {
12806
- authManager.cleanup();
13183
+ logger2.info(chalk3.green("[UPLOAD] AI Blame uploads finalized successfully"));
13184
+ } catch (error) {
13185
+ logger2.error("[UPLOAD] Finalize threw error:", error);
13186
+ throw error;
12807
13187
  }
12808
13188
  }
13189
+ async function uploadAiBlameCommandHandler(args) {
13190
+ await uploadAiBlameHandler({ args });
13191
+ }
12809
13192
 
12810
- // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
12811
- import Debug11 from "debug";
12812
-
12813
- // src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
12814
- import Debug10 from "debug";
12815
- import parseDiff from "parse-diff";
12816
- import { z as z28 } from "zod";
13193
+ // src/features/analysis/graphql/tracy-batch-upload.ts
13194
+ var gzipAsync = promisify(gzip);
13195
+ var debug9 = Debug8("mobbdev:tracy-batch-upload");
13196
+ var MAX_BATCH_PAYLOAD_BYTES = 3 * 1024 * 1024;
12817
13197
 
12818
- // src/features/analysis/utils/by_key.ts
12819
- function keyBy(array, keyBy2) {
12820
- return array.reduce((acc, item) => {
12821
- return { ...acc, [item[keyBy2]]: item };
12822
- }, {});
13198
+ // src/mcp/services/types.ts
13199
+ function detectIDE() {
13200
+ const env3 = process.env;
13201
+ if (env3["CURSOR_TRACE_ID"] || env3["CURSOR_SESSION_ID"]) return "cursor";
13202
+ if (env3["WINDSURF_IPC_HOOK"] || env3["WINDSURF_PID"]) return "windsurf";
13203
+ if (env3["CLAUDE_DESKTOP"] || env3["ANTHROPIC_CLAUDE"]) return "claude";
13204
+ if (env3["WEBSTORM_VM_OPTIONS"] || env3["IDEA_VM_OPTIONS"] || env3["JETBRAINS_IDE"])
13205
+ return "webstorm";
13206
+ if (env3["VSCODE_IPC_HOOK"] || env3["VSCODE_PID"]) return "vscode";
13207
+ const termProgram = env3["TERM_PROGRAM"]?.toLowerCase();
13208
+ if (termProgram === "windsurf") return "windsurf";
13209
+ if (termProgram === "vscode") return "vscode";
13210
+ return void 0;
13211
+ }
13212
+ function createMcpLoginContext(trigger) {
13213
+ return {
13214
+ trigger,
13215
+ source: "mcp",
13216
+ ide: detectIDE()
13217
+ };
13218
+ }
13219
+ function buildLoginUrl(baseUrl, loginId, hostname, context) {
13220
+ const url = new URL(`${baseUrl}/${loginId}`);
13221
+ url.searchParams.set("hostname", hostname);
13222
+ url.searchParams.set("trigger", context.trigger);
13223
+ url.searchParams.set("source", context.source);
13224
+ if (context.ide) {
13225
+ url.searchParams.set("ide", context.ide);
13226
+ }
13227
+ return url.toString();
12823
13228
  }
12824
13229
 
12825
- // src/features/analysis/utils/send_report.ts
12826
- import Debug8 from "debug";
12827
- init_client_generates();
12828
- var debug9 = Debug8("mobbdev:index");
12829
- async function sendReport({
12830
- spinner,
12831
- submitVulnerabilityReportVariables,
12832
- gqlClient,
12833
- polling
12834
- }) {
12835
- try {
12836
- const submitRes = await gqlClient.submitVulnerabilityReport(
12837
- submitVulnerabilityReportVariables
12838
- );
12839
- if (submitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
12840
- debug9("error submit vul report %s", submitRes);
12841
- throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
13230
+ // src/commands/AuthManager.ts
13231
+ var debug10 = Debug9("mobbdev:auth");
13232
+ var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
13233
+ var LOGIN_CHECK_DELAY = 5 * 1e3;
13234
+ var _AuthManager = class _AuthManager {
13235
+ constructor(webAppUrl, apiUrl) {
13236
+ __publicField(this, "publicKey");
13237
+ __publicField(this, "privateKey");
13238
+ __publicField(this, "loginId");
13239
+ __publicField(this, "gqlClient");
13240
+ __publicField(this, "currentBrowserUrl");
13241
+ __publicField(this, "authenticated", null);
13242
+ __publicField(this, "resolvedWebAppUrl");
13243
+ __publicField(this, "resolvedApiUrl");
13244
+ this.resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
13245
+ this.resolvedApiUrl = apiUrl || API_URL;
13246
+ }
13247
+ openUrlInBrowser() {
13248
+ if (this.currentBrowserUrl) {
13249
+ open(this.currentBrowserUrl);
13250
+ return true;
12842
13251
  }
12843
- spinner.update({ text: progressMassages.processingVulnerabilityReport });
12844
- const callback = (_analysisId) => spinner.update({
12845
- text: "\u2699\uFE0F Vulnerability report processed successfully"
13252
+ return false;
13253
+ }
13254
+ async waitForAuthentication() {
13255
+ let newApiToken = null;
13256
+ for (let i = 0; i < _AuthManager.loginMaxWait / LOGIN_CHECK_DELAY; i++) {
13257
+ newApiToken = await this.getApiToken();
13258
+ if (newApiToken) {
13259
+ break;
13260
+ }
13261
+ await sleep(LOGIN_CHECK_DELAY);
13262
+ }
13263
+ if (!newApiToken) {
13264
+ return false;
13265
+ }
13266
+ this.gqlClient = new GQLClient({
13267
+ apiKey: newApiToken,
13268
+ type: "apiKey",
13269
+ apiUrl: this.resolvedApiUrl
12846
13270
  });
12847
- const callbackStates = [
12848
- "Digested" /* Digested */,
12849
- "Finished" /* Finished */
12850
- ];
12851
- if (polling) {
12852
- debug9("[sendReport] Using POLLING mode for analysis state updates");
12853
- await gqlClient.pollForAnalysisState({
12854
- analysisId: submitRes.submitVulnerabilityReport.fixReportId,
12855
- callback,
12856
- callbackStates,
12857
- timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
13271
+ const loginSuccess = await this.gqlClient.validateUserToken();
13272
+ if (loginSuccess) {
13273
+ configStore.set("apiToken", newApiToken);
13274
+ this.authenticated = true;
13275
+ return true;
13276
+ }
13277
+ return false;
13278
+ }
13279
+ /**
13280
+ * Checks if the user is already authenticated
13281
+ */
13282
+ async isAuthenticated() {
13283
+ if (this.authenticated === null) {
13284
+ const result = await this.checkAuthentication();
13285
+ this.authenticated = result.isAuthenticated;
13286
+ if (!result.isAuthenticated) {
13287
+ debug10("isAuthenticated: false \u2014 %s", result.message);
13288
+ }
13289
+ }
13290
+ return this.authenticated;
13291
+ }
13292
+ /**
13293
+ * Private function to check if the user is authenticated with the server
13294
+ */
13295
+ async checkAuthentication(apiKey) {
13296
+ try {
13297
+ if (!this.gqlClient) {
13298
+ this.gqlClient = this.getGQLClient(apiKey);
13299
+ }
13300
+ const isConnected = await this.gqlClient.verifyApiConnection();
13301
+ if (!isConnected) {
13302
+ return {
13303
+ isAuthenticated: false,
13304
+ message: "Failed to connect to Mobb server"
13305
+ };
13306
+ }
13307
+ const userVerify = await this.gqlClient.validateUserToken();
13308
+ if (!userVerify) {
13309
+ return {
13310
+ isAuthenticated: false,
13311
+ message: "User token validation failed"
13312
+ };
13313
+ }
13314
+ } catch (error) {
13315
+ return {
13316
+ isAuthenticated: false,
13317
+ message: error instanceof Error ? error.message : "Unknown authentication error"
13318
+ };
13319
+ }
13320
+ return { isAuthenticated: true, message: "Successfully authenticated" };
13321
+ }
13322
+ /**
13323
+ * Generates a login URL for manual authentication
13324
+ */
13325
+ async generateLoginUrl(loginContext) {
13326
+ try {
13327
+ if (!this.gqlClient) {
13328
+ this.gqlClient = this.getGQLClient();
13329
+ }
13330
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
13331
+ modulusLength: 2048
12858
13332
  });
12859
- } else {
12860
- debug9("[sendReport] Using WEBSOCKET mode for analysis state updates");
12861
- await gqlClient.subscribeToAnalysis({
12862
- subscribeToAnalysisParams: {
12863
- analysisId: submitRes.submitVulnerabilityReport.fixReportId
12864
- },
12865
- callback,
12866
- callbackStates,
12867
- timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
13333
+ this.publicKey = publicKey;
13334
+ this.privateKey = privateKey;
13335
+ this.loginId = await this.gqlClient.createCliLogin({
13336
+ publicKey: this.publicKey.export({ format: "pem", type: "pkcs1" }).toString()
12868
13337
  });
13338
+ const webLoginUrl = `${this.resolvedWebAppUrl}/cli-login`;
13339
+ const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, this.loginId, os3.hostname(), loginContext) : `${webLoginUrl}/${this.loginId}?hostname=${os3.hostname()}`;
13340
+ this.currentBrowserUrl = browserUrl;
13341
+ return browserUrl;
13342
+ } catch (error) {
13343
+ console.error("Failed to generate login URL:", error);
13344
+ return null;
12869
13345
  }
12870
- return submitRes;
12871
- } catch (e) {
12872
- spinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
12873
- throw e;
12874
13346
  }
12875
- }
12876
-
12877
- // src/features/analysis/utils/index.ts
12878
- function getFromArraySafe(array) {
13347
+ /**
13348
+ * Retrieves and decrypts the API token after authentication
13349
+ */
13350
+ async getApiToken() {
13351
+ if (!this.gqlClient || !this.loginId || !this.privateKey) {
13352
+ return null;
13353
+ }
13354
+ const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
13355
+ loginId: this.loginId
13356
+ });
13357
+ if (encryptedApiToken) {
13358
+ return crypto.privateDecrypt(
13359
+ this.privateKey,
13360
+ Buffer.from(encryptedApiToken, "base64")
13361
+ ).toString("utf-8");
13362
+ }
13363
+ return null;
13364
+ }
13365
+ /**
13366
+ * Returns true if a non-empty API token is stored in the configStore.
13367
+ * Used for diagnostics — does NOT validate the token.
13368
+ */
13369
+ hasStoredToken() {
13370
+ const token = configStore.get("apiToken");
13371
+ return typeof token === "string" && token.length > 0;
13372
+ }
13373
+ /**
13374
+ * Gets the current GQL client (if authenticated)
13375
+ */
13376
+ getGQLClient(inputApiKey) {
13377
+ if (this.gqlClient === void 0) {
13378
+ this.gqlClient = new GQLClient({
13379
+ apiKey: inputApiKey || configStore.get("apiToken") || "",
13380
+ type: "apiKey",
13381
+ apiUrl: this.resolvedApiUrl
13382
+ });
13383
+ }
13384
+ return this.gqlClient;
13385
+ }
13386
+ /**
13387
+ * Assigns a GQL client instance to the AuthManager, and resets auth state
13388
+ * @param gqlClient The GQL client instance to set
13389
+ */
13390
+ setGQLClient(gqlClient) {
13391
+ this.gqlClient = gqlClient;
13392
+ this.cleanup();
13393
+ }
13394
+ /**
13395
+ * Cleans up any active login session
13396
+ */
13397
+ cleanup() {
13398
+ this.publicKey = void 0;
13399
+ this.privateKey = void 0;
13400
+ this.loginId = void 0;
13401
+ this.authenticated = null;
13402
+ this.currentBrowserUrl = null;
13403
+ }
13404
+ };
13405
+ /** Maximum time (ms) to wait for login authentication. Override in tests for faster failures. */
13406
+ __publicField(_AuthManager, "loginMaxWait", LOGIN_MAX_WAIT);
13407
+ var AuthManager = _AuthManager;
13408
+
13409
+ // src/commands/handleMobbLogin.ts
13410
+ var debug11 = Debug10("mobbdev:commands");
13411
+ var LOGIN_MAX_WAIT2 = 10 * 60 * 1e3;
13412
+ var LOGIN_CHECK_DELAY2 = 5 * 1e3;
13413
+ 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(
13414
+ "press any key to continue"
13415
+ )};`;
13416
+ async function getAuthenticatedGQLClient({
13417
+ inputApiKey = "",
13418
+ isSkipPrompts = true,
13419
+ apiUrl,
13420
+ webAppUrl
13421
+ }) {
13422
+ debug11(
13423
+ "getAuthenticatedGQLClient called with: apiUrl=%s, webAppUrl=%s",
13424
+ apiUrl || "undefined",
13425
+ webAppUrl || "undefined"
13426
+ );
13427
+ const authManager = new AuthManager(webAppUrl, apiUrl);
13428
+ let gqlClient = authManager.getGQLClient(inputApiKey);
13429
+ gqlClient = await handleMobbLogin({
13430
+ inGqlClient: gqlClient,
13431
+ skipPrompts: isSkipPrompts,
13432
+ apiUrl,
13433
+ webAppUrl
13434
+ });
13435
+ return gqlClient;
13436
+ }
13437
+ async function handleMobbLogin({
13438
+ inGqlClient,
13439
+ apiKey,
13440
+ skipPrompts,
13441
+ apiUrl,
13442
+ webAppUrl,
13443
+ loginContext
13444
+ }) {
13445
+ debug11(
13446
+ "handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
13447
+ apiUrl || "fallback",
13448
+ apiUrl || "fallback",
13449
+ webAppUrl || "fallback",
13450
+ webAppUrl || "fallback"
13451
+ );
13452
+ const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
13453
+ const authManager = new AuthManager(webAppUrl, apiUrl);
13454
+ authManager.setGQLClient(inGqlClient);
13455
+ try {
13456
+ const isAuthenticated = await authManager.isAuthenticated();
13457
+ if (isAuthenticated) {
13458
+ createSpinner5().start().success({
13459
+ text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
13460
+ });
13461
+ return authManager.getGQLClient();
13462
+ }
13463
+ } catch (error) {
13464
+ debug11("Authentication check failed:", error);
13465
+ }
13466
+ if (apiKey) {
13467
+ createSpinner5().start().error({
13468
+ text: "\u{1F513} Login to Mobb failed: The provided API key does not match any configured API key on the system"
13469
+ });
13470
+ throw new CliError(
13471
+ "Login to Mobb failed: The provided API key does not match any configured API key on the system"
13472
+ );
13473
+ }
13474
+ const loginSpinner = createSpinner5().start();
13475
+ if (!skipPrompts) {
13476
+ loginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
13477
+ await keypress();
13478
+ }
13479
+ loginSpinner.update({
13480
+ text: "\u{1F513} Waiting for Mobb login..."
13481
+ });
13482
+ try {
13483
+ const loginUrl = await authManager.generateLoginUrl(loginContext);
13484
+ if (!loginUrl) {
13485
+ loginSpinner.error({
13486
+ text: "Failed to generate login URL"
13487
+ });
13488
+ throw new CliError("Failed to generate login URL");
13489
+ }
13490
+ !skipPrompts && console.log(
13491
+ `If the page does not open automatically, kindly access it through ${loginUrl}.`
13492
+ );
13493
+ authManager.openUrlInBrowser();
13494
+ const authSuccess = await authManager.waitForAuthentication();
13495
+ if (!authSuccess) {
13496
+ loginSpinner.error({
13497
+ text: "Login timeout error"
13498
+ });
13499
+ throw new CliError("Login timeout error");
13500
+ }
13501
+ loginSpinner.success({
13502
+ text: `\u{1F513} Login to Mobb successful!`
13503
+ });
13504
+ return authManager.getGQLClient();
13505
+ } finally {
13506
+ authManager.cleanup();
13507
+ }
13508
+ }
13509
+
13510
+ // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
13511
+ import Debug14 from "debug";
13512
+
13513
+ // src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
13514
+ import Debug13 from "debug";
13515
+ import parseDiff from "parse-diff";
13516
+ import { z as z29 } from "zod";
13517
+
13518
+ // src/features/analysis/utils/by_key.ts
13519
+ function keyBy(array, keyBy2) {
13520
+ return array.reduce((acc, item) => {
13521
+ return { ...acc, [item[keyBy2]]: item };
13522
+ }, {});
13523
+ }
13524
+
13525
+ // src/features/analysis/utils/send_report.ts
13526
+ import Debug11 from "debug";
13527
+ init_client_generates();
13528
+ var debug12 = Debug11("mobbdev:index");
13529
+ async function sendReport({
13530
+ spinner,
13531
+ submitVulnerabilityReportVariables,
13532
+ gqlClient,
13533
+ polling
13534
+ }) {
13535
+ try {
13536
+ const submitRes = await gqlClient.submitVulnerabilityReport(
13537
+ submitVulnerabilityReportVariables
13538
+ );
13539
+ if (submitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
13540
+ debug12("error submit vul report %s", submitRes);
13541
+ throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
13542
+ }
13543
+ spinner.update({ text: progressMassages.processingVulnerabilityReport });
13544
+ const callback = (_analysisId) => spinner.update({
13545
+ text: "\u2699\uFE0F Vulnerability report processed successfully"
13546
+ });
13547
+ const callbackStates = [
13548
+ "Digested" /* Digested */,
13549
+ "Finished" /* Finished */
13550
+ ];
13551
+ if (polling) {
13552
+ debug12("[sendReport] Using POLLING mode for analysis state updates");
13553
+ await gqlClient.pollForAnalysisState({
13554
+ analysisId: submitRes.submitVulnerabilityReport.fixReportId,
13555
+ callback,
13556
+ callbackStates,
13557
+ timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
13558
+ });
13559
+ } else {
13560
+ debug12("[sendReport] Using WEBSOCKET mode for analysis state updates");
13561
+ await gqlClient.subscribeToAnalysis({
13562
+ subscribeToAnalysisParams: {
13563
+ analysisId: submitRes.submitVulnerabilityReport.fixReportId
13564
+ },
13565
+ callback,
13566
+ callbackStates,
13567
+ timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
13568
+ });
13569
+ }
13570
+ return submitRes;
13571
+ } catch (e) {
13572
+ spinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
13573
+ throw e;
13574
+ }
13575
+ }
13576
+
13577
+ // src/features/analysis/utils/index.ts
13578
+ function getFromArraySafe(array) {
12879
13579
  return array.reduce((acc, nullableItem) => {
12880
13580
  if (nullableItem) {
12881
13581
  acc.push(nullableItem);
@@ -12900,10 +13600,10 @@ var scannerToFriendlyString = {
12900
13600
  };
12901
13601
 
12902
13602
  // src/features/analysis/add_fix_comments_for_pr/utils/buildCommentBody.ts
12903
- import Debug9 from "debug";
12904
- import { z as z27 } from "zod";
13603
+ import Debug12 from "debug";
13604
+ import { z as z28 } from "zod";
12905
13605
  init_client_generates();
12906
- var debug10 = Debug9("mobbdev:handle-finished-analysis");
13606
+ var debug13 = Debug12("mobbdev:handle-finished-analysis");
12907
13607
  var getCommitFixButton = (commitUrl) => `<a href="${commitUrl}"><img src=${COMMIT_FIX_SVG}></a>`;
12908
13608
  function buildFixCommentBody({
12909
13609
  fix,
@@ -12955,14 +13655,14 @@ function buildFixCommentBody({
12955
13655
  });
12956
13656
  const issueType = getIssueTypeFriendlyString(fix.safeIssueType);
12957
13657
  const title = `# ${MobbIconMarkdown} ${issueType} fix is ready`;
12958
- const validFixParseRes = z27.object({
13658
+ const validFixParseRes = z28.object({
12959
13659
  patchAndQuestions: PatchAndQuestionsZ,
12960
- safeIssueLanguage: z27.nativeEnum(IssueLanguage_Enum),
12961
- severityText: z27.nativeEnum(Vulnerability_Severity_Enum),
12962
- safeIssueType: z27.nativeEnum(IssueType_Enum)
13660
+ safeIssueLanguage: z28.nativeEnum(IssueLanguage_Enum),
13661
+ severityText: z28.nativeEnum(Vulnerability_Severity_Enum),
13662
+ safeIssueType: z28.nativeEnum(IssueType_Enum)
12963
13663
  }).safeParse(fix);
12964
13664
  if (!validFixParseRes.success) {
12965
- debug10(
13665
+ debug13(
12966
13666
  `fix ${fixId} has custom issue type or language, therefore the commit description will not be added`,
12967
13667
  validFixParseRes.error
12968
13668
  );
@@ -13026,7 +13726,7 @@ ${issuePageLink}`;
13026
13726
  }
13027
13727
 
13028
13728
  // src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
13029
- var debug11 = Debug10("mobbdev:handle-finished-analysis");
13729
+ var debug14 = Debug13("mobbdev:handle-finished-analysis");
13030
13730
  function calculateRanges(integers) {
13031
13731
  if (integers.length === 0) {
13032
13732
  return [];
@@ -13060,7 +13760,7 @@ function deleteAllPreviousComments({
13060
13760
  try {
13061
13761
  return scm.deleteComment({ comment_id: comment.id });
13062
13762
  } catch (e) {
13063
- debug11("delete comment failed %s", e);
13763
+ debug14("delete comment failed %s", e);
13064
13764
  return Promise.resolve();
13065
13765
  }
13066
13766
  });
@@ -13076,7 +13776,7 @@ function deleteAllPreviousGeneralPrComments(params) {
13076
13776
  try {
13077
13777
  return scm.deleteGeneralPrComment({ commentId: comment.id });
13078
13778
  } catch (e) {
13079
- debug11("delete comment failed %s", e);
13779
+ debug14("delete comment failed %s", e);
13080
13780
  return Promise.resolve();
13081
13781
  }
13082
13782
  });
@@ -13193,7 +13893,7 @@ async function getRelevantVulenrabilitiesFromDiff(params) {
13193
13893
  });
13194
13894
  const lineAddedRanges = calculateRanges(fileNumbers);
13195
13895
  const fileFilter = {
13196
- path: z28.string().parse(file.to),
13896
+ path: z29.string().parse(file.to),
13197
13897
  ranges: lineAddedRanges.map(([startLine, endLine]) => ({
13198
13898
  endLine,
13199
13899
  startLine
@@ -13220,7 +13920,7 @@ async function postAnalysisInsightComment(params) {
13220
13920
  fixablePrVuls,
13221
13921
  nonFixablePrVuls
13222
13922
  } = prVulenrabilities;
13223
- debug11({
13923
+ debug14({
13224
13924
  fixablePrVuls,
13225
13925
  nonFixablePrVuls,
13226
13926
  vulnerabilitiesOutsidePr,
@@ -13275,7 +13975,7 @@ ${contactUsMarkdown}`;
13275
13975
  }
13276
13976
 
13277
13977
  // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
13278
- var debug12 = Debug11("mobbdev:handle-finished-analysis");
13978
+ var debug15 = Debug14("mobbdev:handle-finished-analysis");
13279
13979
  async function addFixCommentsForPr({
13280
13980
  analysisId,
13281
13981
  scm: _scm,
@@ -13287,7 +13987,7 @@ async function addFixCommentsForPr({
13287
13987
  }
13288
13988
  const scm = _scm;
13289
13989
  const getAnalysisRes = await gqlClient.getAnalysis(analysisId);
13290
- debug12("getAnalysis %o", getAnalysisRes);
13990
+ debug15("getAnalysis %o", getAnalysisRes);
13291
13991
  const {
13292
13992
  vulnerabilityReport: {
13293
13993
  projectId,
@@ -13397,8 +14097,8 @@ ${contextString}` : description;
13397
14097
 
13398
14098
  // src/features/analysis/auto_pr_handler.ts
13399
14099
  init_client_generates();
13400
- import Debug12 from "debug";
13401
- var debug13 = Debug12("mobbdev:handleAutoPr");
14100
+ import Debug15 from "debug";
14101
+ var debug16 = Debug15("mobbdev:handleAutoPr");
13402
14102
  async function handleAutoPr(params) {
13403
14103
  const {
13404
14104
  gqlClient,
@@ -13419,7 +14119,7 @@ async function handleAutoPr(params) {
13419
14119
  prId,
13420
14120
  prStrategy: createOnePr ? "CONDENSE" /* Condense */ : "SPREAD" /* Spread */
13421
14121
  });
13422
- debug13("auto pr analysis res %o", autoPrAnalysisRes);
14122
+ debug16("auto pr analysis res %o", autoPrAnalysisRes);
13423
14123
  if (autoPrAnalysisRes.autoPrAnalysis?.__typename === "AutoPrError") {
13424
14124
  createAutoPrSpinner.error({
13425
14125
  text: `\u{1F504} Automatic pull request failed - ${autoPrAnalysisRes.autoPrAnalysis.error}`
@@ -13441,14 +14141,14 @@ async function handleAutoPr(params) {
13441
14141
  };
13442
14142
  const callbackStates = ["Finished" /* Finished */];
13443
14143
  if (polling) {
13444
- debug13("[handleAutoPr] Using POLLING mode for analysis state updates");
14144
+ debug16("[handleAutoPr] Using POLLING mode for analysis state updates");
13445
14145
  return await gqlClient.pollForAnalysisState({
13446
14146
  analysisId,
13447
14147
  callback,
13448
14148
  callbackStates
13449
14149
  });
13450
14150
  } else {
13451
- debug13("[handleAutoPr] Using WEBSOCKET mode for analysis state updates");
14151
+ debug16("[handleAutoPr] Using WEBSOCKET mode for analysis state updates");
13452
14152
  return await gqlClient.subscribeToAnalysis({
13453
14153
  subscribeToAnalysisParams: {
13454
14154
  analysisId
@@ -13461,15 +14161,15 @@ async function handleAutoPr(params) {
13461
14161
 
13462
14162
  // src/features/analysis/git.ts
13463
14163
  init_GitService();
13464
- import Debug13 from "debug";
13465
- var debug14 = Debug13("mobbdev:git");
14164
+ import Debug16 from "debug";
14165
+ var debug17 = Debug16("mobbdev:git");
13466
14166
  async function getGitInfo(srcDirPath) {
13467
- debug14("getting git info for %s", srcDirPath);
14167
+ debug17("getting git info for %s", srcDirPath);
13468
14168
  const gitService = new GitService(srcDirPath);
13469
14169
  try {
13470
14170
  const validationResult = await gitService.validateRepository();
13471
14171
  if (!validationResult.isValid) {
13472
- debug14("folder is not a git repo");
14172
+ debug17("folder is not a git repo");
13473
14173
  return {
13474
14174
  success: false,
13475
14175
  hash: void 0,
@@ -13484,9 +14184,9 @@ async function getGitInfo(srcDirPath) {
13484
14184
  };
13485
14185
  } catch (e) {
13486
14186
  if (e instanceof Error) {
13487
- debug14("failed to run git %o", e);
14187
+ debug17("failed to run git %o", e);
13488
14188
  if (e.message.includes(" spawn ")) {
13489
- debug14("git cli not installed");
14189
+ debug17("git cli not installed");
13490
14190
  } else {
13491
14191
  throw e;
13492
14192
  }
@@ -13498,22 +14198,22 @@ async function getGitInfo(srcDirPath) {
13498
14198
  // src/features/analysis/pack.ts
13499
14199
  init_configs();
13500
14200
  import fs9 from "fs";
13501
- import path7 from "path";
14201
+ import path8 from "path";
13502
14202
  import AdmZip from "adm-zip";
13503
- import Debug14 from "debug";
14203
+ import Debug17 from "debug";
13504
14204
  import { globby } from "globby";
13505
14205
  import { isBinary as isBinary2 } from "istextorbinary";
13506
14206
  import { simpleGit as simpleGit3 } from "simple-git";
13507
14207
  import { parseStringPromise } from "xml2js";
13508
- import { z as z29 } from "zod";
13509
- var debug15 = Debug14("mobbdev:pack");
13510
- var FPR_SOURCE_CODE_FILE_MAPPING_SCHEMA = z29.object({
13511
- properties: z29.object({
13512
- entry: z29.array(
13513
- z29.object({
13514
- _: z29.string(),
13515
- $: z29.object({
13516
- key: z29.string()
14208
+ import { z as z30 } from "zod";
14209
+ var debug18 = Debug17("mobbdev:pack");
14210
+ var FPR_SOURCE_CODE_FILE_MAPPING_SCHEMA = z30.object({
14211
+ properties: z30.object({
14212
+ entry: z30.array(
14213
+ z30.object({
14214
+ _: z30.string(),
14215
+ $: z30.object({
14216
+ key: z30.string()
13517
14217
  })
13518
14218
  })
13519
14219
  )
@@ -13528,7 +14228,7 @@ function getManifestFilesSuffixes() {
13528
14228
  return ["package.json", "pom.xml"];
13529
14229
  }
13530
14230
  async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
13531
- debug15("pack folder %s", srcDirPath);
14231
+ debug18("pack folder %s", srcDirPath);
13532
14232
  let git = void 0;
13533
14233
  try {
13534
14234
  git = simpleGit3({
@@ -13538,13 +14238,13 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
13538
14238
  });
13539
14239
  await git.status();
13540
14240
  } catch (e) {
13541
- debug15("failed to run git %o", e);
14241
+ debug18("failed to run git %o", e);
13542
14242
  git = void 0;
13543
14243
  if (e instanceof Error) {
13544
14244
  if (e.message.includes(" spawn ")) {
13545
- debug15("git cli not installed");
14245
+ debug18("git cli not installed");
13546
14246
  } else if (e.message.includes("not a git repository")) {
13547
- debug15("folder is not a git repo");
14247
+ debug18("folder is not a git repo");
13548
14248
  } else {
13549
14249
  throw e;
13550
14250
  }
@@ -13559,23 +14259,23 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
13559
14259
  followSymbolicLinks: false,
13560
14260
  dot: true
13561
14261
  });
13562
- debug15("files found %d", filepaths.length);
14262
+ debug18("files found %d", filepaths.length);
13563
14263
  const zip = new AdmZip();
13564
- debug15("compressing files");
14264
+ debug18("compressing files");
13565
14265
  for (const filepath of filepaths) {
13566
- const absFilepath = path7.join(srcDirPath, filepath.toString());
14266
+ const absFilepath = path8.join(srcDirPath, filepath.toString());
13567
14267
  if (!isIncludeAllFiles) {
13568
14268
  vulnFiles = vulnFiles.concat(getManifestFilesSuffixes());
13569
14269
  if (!endsWithAny(
13570
- absFilepath.toString().replaceAll(path7.win32.sep, path7.posix.sep),
14270
+ absFilepath.toString().replaceAll(path8.win32.sep, path8.posix.sep),
13571
14271
  vulnFiles
13572
14272
  )) {
13573
- debug15("ignoring %s because it is not a vulnerability file", filepath);
14273
+ debug18("ignoring %s because it is not a vulnerability file", filepath);
13574
14274
  continue;
13575
14275
  }
13576
14276
  }
13577
14277
  if (fs9.lstatSync(absFilepath).size > MCP_MAX_FILE_SIZE) {
13578
- debug15("ignoring %s because the size is > 5MB", filepath);
14278
+ debug18("ignoring %s because the size is > 5MB", filepath);
13579
14279
  continue;
13580
14280
  }
13581
14281
  let data;
@@ -13589,16 +14289,16 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
13589
14289
  data = fs9.readFileSync(absFilepath);
13590
14290
  }
13591
14291
  if (isBinary2(null, data)) {
13592
- debug15("ignoring %s because is seems to be a binary file", filepath);
14292
+ debug18("ignoring %s because is seems to be a binary file", filepath);
13593
14293
  continue;
13594
14294
  }
13595
14295
  zip.addFile(filepath.toString(), data);
13596
14296
  }
13597
- debug15("get zip file buffer");
14297
+ debug18("get zip file buffer");
13598
14298
  return zip.toBuffer();
13599
14299
  }
13600
14300
  async function repackFpr(fprPath) {
13601
- debug15("repack fpr file %s", fprPath);
14301
+ debug18("repack fpr file %s", fprPath);
13602
14302
  const zipIn = new AdmZip(fprPath);
13603
14303
  const zipOut = new AdmZip();
13604
14304
  const mappingXML = zipIn.readAsText("src-archive/index.xml", "utf-8");
@@ -13613,7 +14313,7 @@ async function repackFpr(fprPath) {
13613
14313
  zipOut.addFile(realPath, buf);
13614
14314
  }
13615
14315
  }
13616
- debug15("get repacked zip file buffer");
14316
+ debug18("get repacked zip file buffer");
13617
14317
  return zipOut.toBuffer();
13618
14318
  }
13619
14319
 
@@ -13683,12 +14383,12 @@ async function snykArticlePrompt() {
13683
14383
 
13684
14384
  // src/features/analysis/scanners/checkmarx.ts
13685
14385
  import { createRequire } from "module";
13686
- import chalk4 from "chalk";
13687
- import Debug16 from "debug";
14386
+ import chalk5 from "chalk";
14387
+ import Debug19 from "debug";
13688
14388
  import { existsSync } from "fs";
13689
14389
  import { createSpinner as createSpinner2 } from "nanospinner";
13690
14390
  import { type } from "os";
13691
- import path8 from "path";
14391
+ import path9 from "path";
13692
14392
 
13693
14393
  // src/post_install/constants.mjs
13694
14394
  var cxOperatingSystemSupportMessage = `Your operating system does not support checkmarx.
@@ -13696,7 +14396,7 @@ var cxOperatingSystemSupportMessage = `Your operating system does not support ch
13696
14396
 
13697
14397
  // src/utils/child_process.ts
13698
14398
  import cp from "child_process";
13699
- import Debug15 from "debug";
14399
+ import Debug18 from "debug";
13700
14400
  import * as process2 from "process";
13701
14401
  function createFork({ args, processPath, name }, options) {
13702
14402
  const child = cp.fork(processPath, args, {
@@ -13714,16 +14414,16 @@ function createSpawn({ args, processPath, name, cwd }, options) {
13714
14414
  return createChildProcess({ childProcess: child, name }, options);
13715
14415
  }
13716
14416
  function createChildProcess({ childProcess, name }, options) {
13717
- const debug21 = Debug15(`mobbdev:${name}`);
14417
+ const debug23 = Debug18(`mobbdev:${name}`);
13718
14418
  const { display } = options;
13719
14419
  return new Promise((resolve, reject) => {
13720
14420
  let out = "";
13721
14421
  const onData = (chunk) => {
13722
- debug21(`chunk received from ${name} std ${chunk}`);
14422
+ debug23(`chunk received from ${name} std ${chunk}`);
13723
14423
  out += chunk;
13724
14424
  };
13725
14425
  if (!childProcess?.stdout || !childProcess?.stderr) {
13726
- debug21(`unable to fork ${name}`);
14426
+ debug23(`unable to fork ${name}`);
13727
14427
  reject(new Error(`unable to fork ${name}`));
13728
14428
  }
13729
14429
  childProcess.stdout?.on("data", onData);
@@ -13733,18 +14433,18 @@ function createChildProcess({ childProcess, name }, options) {
13733
14433
  childProcess.stderr?.pipe(process2.stderr);
13734
14434
  }
13735
14435
  childProcess.on("exit", (code) => {
13736
- debug21(`${name} exit code ${code}`);
14436
+ debug23(`${name} exit code ${code}`);
13737
14437
  resolve({ message: out, code });
13738
14438
  });
13739
14439
  childProcess.on("error", (err) => {
13740
- debug21(`${name} error %o`, err);
14440
+ debug23(`${name} error %o`, err);
13741
14441
  reject(err);
13742
14442
  });
13743
14443
  });
13744
14444
  }
13745
14445
 
13746
14446
  // src/features/analysis/scanners/checkmarx.ts
13747
- var debug16 = Debug16("mobbdev:checkmarx");
14447
+ var debug19 = Debug19("mobbdev:checkmarx");
13748
14448
  var moduleUrl;
13749
14449
  if (typeof __filename !== "undefined") {
13750
14450
  moduleUrl = __filename;
@@ -13803,1900 +14503,1283 @@ function validateCheckmarxInstallation() {
13803
14503
  existsSync(getCheckmarxPath());
13804
14504
  }
13805
14505
  async function forkCheckmarx(args, { display }) {
13806
- debug16("fork checkmarx with args %o %s", args.join(" "), display);
14506
+ debug19("fork checkmarx with args %o %s", args.join(" "), display);
13807
14507
  return createSpawn(
13808
14508
  { args, processPath: getCheckmarxPath(), name: "checkmarx" },
13809
14509
  { display }
13810
14510
  );
13811
14511
  }
13812
14512
  async function getCheckmarxReport({ reportPath, repositoryRoot, branch, projectName }, { skipPrompts = false }) {
13813
- debug16("get checkmarx report start %s %s", reportPath, repositoryRoot);
14513
+ debug19("get checkmarx report start %s %s", reportPath, repositoryRoot);
13814
14514
  const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
13815
- display: false
13816
- });
13817
- if (loginCode !== CHECKMARX_SUCCESS_CODE) {
13818
- if (skipPrompts) {
13819
- await throwCheckmarxConfigError();
13820
- }
13821
- await startCheckmarxConfigationPrompt();
13822
- await validateCheckamxCredentials();
13823
- }
13824
- const extension = path8.extname(reportPath);
13825
- const filePath = path8.dirname(reportPath);
13826
- const fileName = path8.basename(reportPath, extension);
13827
- const checkmarxCommandArgs = getCheckmarxCommandArgs({
13828
- repoPath: repositoryRoot,
13829
- branch,
13830
- filePath,
13831
- fileName,
13832
- projectName
13833
- });
13834
- console.log("\u280B \u{1F50D} Initiating Checkmarx Scan ");
13835
- const { code: scanCode } = await forkCheckmarx(
13836
- [...SCAN_COMMAND, ...checkmarxCommandArgs],
13837
- {
13838
- display: true
13839
- }
13840
- );
13841
- if (scanCode !== CHECKMARX_SUCCESS_CODE) {
13842
- createSpinner2("\u{1F50D} Something went wrong with the checkmarx scan").start().error();
13843
- throw new CliError();
13844
- }
13845
- await createSpinner2("\u{1F50D} Checkmarx Scan completed").start().success();
13846
- return true;
13847
- }
13848
- async function throwCheckmarxConfigError() {
13849
- await createSpinner2("\u{1F513} Checkmarx is not configued correctly").start().error();
13850
- throw new CliError(
13851
- `Checkmarx is not configued correctly
13852
- you can configure it by using the ${chalk4.bold(
13853
- "cx configure"
13854
- )} command`
13855
- );
13856
- }
13857
- async function validateCheckamxCredentials() {
13858
- console.log(`
13859
- Here's a suggestion for checkmarx configuation:
13860
- ${chalk4.bold("AST Base URI:")} https://ast.checkmarx.net
13861
- ${chalk4.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
13862
- `);
13863
- await forkCheckmarx(CONFIGURE_COMMAND, { display: true });
13864
- const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
13865
- display: false
13866
- });
13867
- if (loginCode !== CHECKMARX_SUCCESS_CODE) {
13868
- const tryAgain = await tryCheckmarxConfiguarationAgain();
13869
- if (!tryAgain) {
13870
- await throwCheckmarxConfigError();
13871
- }
13872
- await validateCheckamxCredentials();
13873
- return;
13874
- }
13875
- await createSpinner2("\u{1F513} Checkmarx configured successfully!").start().success();
13876
- }
13877
-
13878
- // src/features/analysis/scanners/snyk.ts
13879
- import { createRequire as createRequire2 } from "module";
13880
- import chalk5 from "chalk";
13881
- import Debug17 from "debug";
13882
- import { createSpinner as createSpinner3 } from "nanospinner";
13883
- import open2 from "open";
13884
- var debug17 = Debug17("mobbdev:snyk");
13885
- var moduleUrl2;
13886
- if (typeof __filename !== "undefined") {
13887
- moduleUrl2 = __filename;
13888
- } else {
13889
- try {
13890
- const getImportMetaUrl = new Function("return import.meta.url");
13891
- moduleUrl2 = getImportMetaUrl();
13892
- } catch {
13893
- const err = new Error();
13894
- const stack = err.stack || "";
13895
- const match = stack.match(/file:\/\/[^\s)]+/);
13896
- if (match) {
13897
- moduleUrl2 = match[0];
13898
- } else {
13899
- throw new Error("Unable to determine module URL in this environment");
13900
- }
13901
- }
13902
- }
13903
- var costumeRequire2 = createRequire2(moduleUrl2);
13904
- var SNYK_PATH = costumeRequire2.resolve("snyk/bin/snyk");
13905
- var SNYK_ARTICLE_URL = "https://docs.snyk.io/scan-using-snyk/snyk-code/configure-snyk-code#enable-snyk-code";
13906
- debug17("snyk executable path %s", SNYK_PATH);
13907
- async function forkSnyk(args, { display }) {
13908
- debug17("fork snyk with args %o %s", args, display);
13909
- return createFork({ args, processPath: SNYK_PATH, name: "snyk" }, { display });
13910
- }
13911
- async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
13912
- debug17("get snyk report start %s %s", reportPath, repoRoot);
13913
- const config2 = await forkSnyk(["config"], { display: false });
13914
- const { message: configMessage } = config2;
13915
- if (!configMessage.includes("api: ")) {
13916
- const snykLoginSpinner = createSpinner3().start();
13917
- if (!skipPrompts) {
13918
- snykLoginSpinner.update({
13919
- text: "\u{1F513} Login to Snyk is required, press any key to continue"
13920
- });
13921
- await keypress();
13922
- }
13923
- snykLoginSpinner.update({
13924
- text: "\u{1F513} Waiting for Snyk login to complete"
13925
- });
13926
- debug17("no token in the config %s", config2);
13927
- await forkSnyk(["auth"], { display: true });
13928
- snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
13929
- }
13930
- const snykSpinner = createSpinner3("\u{1F50D} Scanning your repo with Snyk ").start();
13931
- const { message: scanOutput } = await forkSnyk(
13932
- ["code", "test", `--sarif-file-output=${reportPath}`, repoRoot],
13933
- { display: true }
13934
- );
13935
- if (scanOutput.includes("Snyk Code is not supported for org")) {
13936
- debug17("snyk code is not enabled %s", scanOutput);
13937
- snykSpinner.error({ text: "\u{1F50D} Snyk configuration needed" });
13938
- const answer = await snykArticlePrompt();
13939
- debug17("answer %s", answer);
13940
- if (answer) {
13941
- debug17("opening the browser");
13942
- await open2(SNYK_ARTICLE_URL);
13943
- }
13944
- console.log(
13945
- chalk5.bgBlue(
13946
- "\nPlease enable Snyk Code in your Snyk account and try again."
13947
- )
13948
- );
13949
- throw Error("snyk is not enbabled");
13950
- }
13951
- snykSpinner.success({ text: "\u{1F50D} Snyk code scan completed" });
13952
- return true;
13953
- }
13954
-
13955
- // src/features/analysis/index.ts
13956
- init_client_generates();
13957
-
13958
- // src/features/analysis/upload-file.ts
13959
- import Debug18 from "debug";
13960
- import fetch3, { File, fileFrom, FormData } from "node-fetch";
13961
- var debug18 = Debug18("mobbdev:upload-file");
13962
- async function uploadFile({
13963
- file,
13964
- url,
13965
- uploadKey,
13966
- uploadFields,
13967
- logger: logger2
13968
- }) {
13969
- const logInfo2 = logger2 || ((_message, _data) => {
13970
- });
13971
- logInfo2(`FileUpload: upload file start ${url}`);
13972
- logInfo2(`FileUpload: upload fields`, uploadFields);
13973
- logInfo2(`FileUpload: upload key ${uploadKey}`);
13974
- debug18("upload file start %s", url);
13975
- debug18("upload fields %o", uploadFields);
13976
- debug18("upload key %s", uploadKey);
13977
- const form = new FormData();
13978
- Object.entries(uploadFields).forEach(([key, value]) => {
13979
- form.append(key, value);
13980
- });
13981
- if (!form.has("key")) {
13982
- form.append("key", uploadKey);
13983
- }
13984
- if (typeof file === "string") {
13985
- debug18("upload file from path %s", file);
13986
- logInfo2(`FileUpload: upload file from path ${file}`);
13987
- form.append("file", await fileFrom(file));
13988
- } else {
13989
- debug18("upload file from buffer");
13990
- logInfo2(`FileUpload: upload file from buffer`);
13991
- form.append("file", new File([new Uint8Array(file)], "file"));
13992
- }
13993
- const agent = getProxyAgent(url);
13994
- const response = await fetch3(url, {
13995
- method: "POST",
13996
- body: form,
13997
- agent
13998
- });
13999
- if (!response.ok) {
14000
- debug18("error from S3 %s %s", response.body, response.status);
14001
- logInfo2(`FileUpload: error from S3 ${response.body} ${response.status}`);
14002
- throw new Error(`Failed to upload the file: ${response.status}`);
14003
- }
14004
- debug18("upload file done");
14005
- logInfo2(`FileUpload: upload file done`);
14006
- }
14007
-
14008
- // src/features/analysis/index.ts
14009
- var { CliError: CliError2, Spinner: Spinner2 } = utils_exports;
14010
- function _getScanSource(command, ci) {
14011
- if (command === "review") return "AUTO_FIXER" /* AutoFixer */;
14012
- const envToCi = [
14013
- ["GITLAB_CI", "CI_GITLAB" /* CiGitlab */],
14014
- ["GITHUB_ACTIONS", "CI_GITHUB" /* CiGithub */],
14015
- ["JENKINS_URL", "CI_JENKINS" /* CiJenkins */],
14016
- ["CIRCLECI", "CI_CIRCLECI" /* CiCircleci */],
14017
- ["TF_BUILD", "CI_AZURE" /* CiAzure */],
14018
- ["bamboo_buildKey", "CI_BAMBOO" /* CiBamboo */]
14019
- ];
14020
- for (const [envKey, source] of envToCi) {
14021
- if (env2[envKey]) {
14022
- return source;
14023
- }
14024
- }
14025
- if (ci) {
14026
- return "CI_UNKNOWN" /* CiUnknown */;
14027
- }
14028
- return "CLI" /* Cli */;
14029
- }
14030
- async function downloadRepo({
14031
- repoUrl,
14032
- authHeaders,
14033
- downloadUrl,
14034
- dirname,
14035
- ci
14036
- }) {
14037
- const { createSpinner: createSpinner5 } = Spinner2({ ci });
14038
- const repoSpinner = createSpinner5("\u{1F4BE} Downloading Repo").start();
14039
- debug19("download repo %s %s %s", repoUrl, dirname);
14040
- const zipFilePath = path9.join(dirname, "repo.zip");
14041
- debug19("download URL: %s auth headers: %o", downloadUrl, authHeaders);
14042
- const response = await fetch4(downloadUrl, {
14043
- method: "GET",
14044
- headers: {
14045
- ...authHeaders
14046
- }
14047
- });
14048
- if (!response.ok) {
14049
- debug19("SCM zipball request failed %s %s", response.body, response.status);
14050
- repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
14051
- throw new Error(`Can't access ${chalk6.bold(repoUrl)}`);
14052
- }
14053
- const fileWriterStream = fs10.createWriteStream(zipFilePath);
14054
- if (!response.body) {
14055
- throw new Error("Response body is empty");
14056
- }
14057
- await pipeline(response.body, fileWriterStream);
14058
- await extract(zipFilePath, { dir: dirname });
14059
- const repoRoot = fs10.readdirSync(dirname, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name)[0];
14060
- if (!repoRoot) {
14061
- throw new Error("Repo root not found");
14062
- }
14063
- debug19("repo root %s", repoRoot);
14064
- repoSpinner.success({ text: "\u{1F4BE} Repo downloaded successfully" });
14065
- return path9.join(dirname, repoRoot);
14066
- }
14067
- var getReportUrl = ({
14068
- organizationId,
14069
- projectId,
14070
- fixReportId
14071
- }) => `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${fixReportId}`;
14072
- var debug19 = Debug19("mobbdev:index");
14073
- async function runAnalysis(params, options) {
14074
- const tmpObj = tmp2.dirSync({
14075
- unsafeCleanup: true
14076
- });
14077
- try {
14078
- return await _scan(
14079
- {
14080
- ...params,
14081
- dirname: tmpObj.name
14082
- },
14083
- options
14084
- );
14085
- } finally {
14086
- tmpObj.removeCallback();
14087
- }
14088
- }
14089
- function _getUrlForScmType({
14090
- scmLibType
14091
- }) {
14092
- const githubAuthUrl = `${WEB_APP_URL}/github-auth`;
14093
- const gitlabAuthUrl = `${WEB_APP_URL}/gitlab-auth`;
14094
- const adoAuthUrl = `${WEB_APP_URL}/ado-auth`;
14095
- switch (scmLibType) {
14096
- case "GITHUB" /* GITHUB */:
14097
- return {
14098
- authUrl: githubAuthUrl
14099
- };
14100
- case "GITLAB" /* GITLAB */:
14101
- return {
14102
- authUrl: gitlabAuthUrl
14103
- };
14104
- case "ADO" /* ADO */:
14105
- return {
14106
- authUrl: adoAuthUrl
14107
- };
14108
- default:
14109
- return {
14110
- authUrl: void 0
14111
- };
14112
- }
14113
- }
14114
- function getBrokerHosts(userOrgsAnUserOrgRoles) {
14115
- const brokerHosts = [];
14116
- if (!userOrgsAnUserOrgRoles) {
14117
- return brokerHosts;
14118
- }
14119
- userOrgsAnUserOrgRoles.forEach((org) => {
14120
- org?.organization?.brokerHosts.forEach((brokerHost) => {
14121
- if (brokerHost) {
14122
- brokerHosts.push(brokerHost);
14123
- }
14124
- });
14125
- });
14126
- return brokerHosts;
14127
- }
14128
- async function getScmTokenInfo(params) {
14129
- const { gqlClient, repo } = params;
14130
- const userInfo2 = await gqlClient.getUserInfo();
14131
- if (!userInfo2) {
14132
- throw new Error("userInfo is null");
14133
- }
14134
- const scmConfigs = getFromArraySafe(userInfo2.scmConfigs);
14135
- return getScmConfig({
14136
- url: repo,
14137
- scmConfigs,
14138
- includeOrgTokens: false,
14139
- brokerHosts: getBrokerHosts(
14140
- userInfo2.userOrganizationsAndUserOrganizationRoles
14141
- )
14142
- });
14143
- }
14144
- async function getReport(params, { skipPrompts }) {
14145
- const {
14146
- scanner,
14147
- repoUrl,
14148
- gqlClient,
14149
- sha,
14150
- dirname,
14151
- reference,
14152
- cxProjectName,
14153
- ci
14154
- } = params;
14155
- const tokenInfo = await getScmTokenInfo({ gqlClient, repo: repoUrl });
14156
- const scm = await createScmLib(
14157
- {
14158
- url: repoUrl,
14159
- accessToken: tokenInfo.accessToken,
14160
- scmOrg: tokenInfo.scmOrg,
14161
- scmType: tokenInfo.scmLibType
14162
- },
14163
- { propagateExceptions: true }
14164
- );
14165
- const downloadUrl = await scm.getDownloadUrl(sha);
14166
- const repositoryRoot = await downloadRepo({
14167
- repoUrl,
14168
- dirname,
14169
- ci,
14170
- authHeaders: scm.getAuthHeaders(),
14171
- downloadUrl
14172
- });
14173
- const reportPath = path9.join(dirname, REPORT_DEFAULT_FILE_NAME);
14174
- switch (scanner) {
14175
- case "snyk":
14176
- await getSnykReport(reportPath, repositoryRoot, { skipPrompts });
14177
- break;
14178
- case "checkmarx":
14179
- if (!cxProjectName) {
14180
- throw new Error("cxProjectName is required for checkmarx scanner");
14181
- }
14182
- await getCheckmarxReport(
14183
- {
14184
- reportPath,
14185
- repositoryRoot,
14186
- branch: reference,
14187
- projectName: cxProjectName
14188
- },
14189
- { skipPrompts }
14190
- );
14191
- break;
14192
- }
14193
- return reportPath;
14194
- }
14195
- async function _scan(params, { skipPrompts = false } = {}) {
14196
- const {
14197
- dirname,
14198
- repo,
14199
- scanFile,
14200
- apiKey,
14201
- ci,
14202
- srcPath,
14203
- commitHash,
14204
- ref,
14205
- experimentalEnabled,
14206
- scanner,
14207
- cxProjectName,
14208
- mobbProjectName,
14209
- githubToken: githubActionToken,
14210
- command,
14211
- organizationId: userOrganizationId,
14212
- autoPr,
14213
- createOnePr,
14214
- commitDirectly,
14215
- pullRequest,
14216
- polling
14217
- } = params;
14218
- debug19("start %s %s", dirname, repo);
14219
- const { createSpinner: createSpinner5 } = Spinner2({ ci });
14220
- skipPrompts = skipPrompts || ci;
14221
- const gqlClient = await getAuthenticatedGQLClient({
14222
- inputApiKey: apiKey,
14223
- isSkipPrompts: skipPrompts
14224
- });
14225
- if (!mobbProjectName) {
14226
- throw new Error("mobbProjectName is required");
14227
- }
14228
- const { projectId, organizationId } = await gqlClient.getLastOrgAndNamedProject({
14229
- projectName: mobbProjectName,
14230
- userDefinedOrganizationId: userOrganizationId
14231
- });
14232
- const {
14233
- uploadS3BucketInfo: { repoUploadInfo, reportUploadInfo }
14234
- } = await gqlClient.uploadS3BucketInfo();
14235
- if (!reportUploadInfo || !repoUploadInfo) {
14236
- throw new Error("uploadS3BucketInfo is null");
14237
- }
14238
- let reportPath = scanFile;
14239
- if (srcPath) {
14240
- return await uploadExistingRepo();
14241
- }
14242
- if (!repo) {
14243
- throw new Error("repo is required in case srcPath is not provided");
14244
- }
14245
- const tokenInfo = await getScmTokenInfo({ gqlClient, repo });
14246
- const validateRes = await gqlClient.validateRepoUrl({ repoUrl: repo });
14247
- const isRepoAvailable = validateRes.validateRepoUrl?.__typename === "RepoValidationSuccess";
14248
- const cloudScmLibType = getCloudScmLibTypeFromUrl(repo);
14249
- const { authUrl: scmAuthUrl } = _getUrlForScmType({
14250
- scmLibType: cloudScmLibType
14251
- });
14252
- if (!isRepoAvailable) {
14253
- if (ci || !cloudScmLibType || !scmAuthUrl) {
14254
- const errorMessage = scmAuthUrl ? `Cannot access repo ${repo}. Make sure that the repo is accessible and the SCM token configured on Mobb is correct.` : `Cannot access repo ${repo} with the provided token, please visit ${scmAuthUrl} to refresh your source control management system token`;
14255
- throw new Error(errorMessage);
14256
- }
14257
- if (cloudScmLibType && scmAuthUrl) {
14258
- await handleScmIntegration(tokenInfo.accessToken, scmAuthUrl, repo);
14259
- const repoValidationResponse = await gqlClient.validateRepoUrl({
14260
- repoUrl: repo
14261
- });
14262
- const isRepoAvailable2 = repoValidationResponse.validateRepoUrl?.__typename === "RepoValidationSuccess";
14263
- if (!isRepoAvailable2) {
14264
- throw new Error(
14265
- `Cannot access repo ${repo} with the provided credentials: ${repoValidationResponse.validateRepoUrl?.__typename}`
14266
- );
14267
- }
14268
- }
14269
- }
14270
- const revalidateRes = await gqlClient.validateRepoUrl({ repoUrl: repo });
14271
- if (revalidateRes.validateRepoUrl?.__typename !== "RepoValidationSuccess") {
14272
- throw new Error(
14273
- `could not reach repo ${repo}: ${revalidateRes.validateRepoUrl?.__typename}`
14274
- );
14275
- }
14276
- const reference = ref ?? revalidateRes.validateRepoUrl.defaultBranch;
14277
- const getReferenceDataRes = await gqlClient.getReferenceData({
14278
- reference,
14279
- repoUrl: repo
14280
- });
14281
- if (getReferenceDataRes.gitReference?.__typename !== "GitReferenceData") {
14282
- throw new Error(
14283
- `Could not get reference data for ${reference}: ${getReferenceDataRes.gitReference?.__typename}`
14284
- );
14285
- }
14286
- const { sha } = getReferenceDataRes.gitReference;
14287
- debug19("project id %s", projectId);
14288
- debug19("default branch %s", reference);
14289
- if (command === "scan") {
14290
- reportPath = await getReport(
14291
- {
14292
- scanner: SupportedScannersZ.parse(scanner),
14293
- repoUrl: repo,
14294
- sha,
14295
- gqlClient,
14296
- cxProjectName,
14297
- dirname,
14298
- reference,
14299
- ci
14300
- },
14301
- { skipPrompts }
14302
- );
14303
- }
14304
- const shouldScan = !reportPath;
14305
- const uploadReportSpinner = createSpinner5("\u{1F4C1} Uploading Report").start();
14306
- try {
14307
- if (reportPath) {
14308
- await uploadFile({
14309
- file: reportPath,
14310
- url: reportUploadInfo.url,
14311
- uploadFields: JSON.parse(reportUploadInfo.uploadFieldsJSON),
14312
- uploadKey: reportUploadInfo.uploadKey
14313
- });
14314
- }
14315
- } catch (e) {
14316
- uploadReportSpinner.error({ text: "\u{1F4C1} Report upload failed" });
14317
- throw e;
14318
- }
14319
- await _digestReport({
14320
- gqlClient,
14321
- fixReportId: reportUploadInfo.fixReportId,
14322
- projectId,
14323
- command,
14324
- ci,
14325
- repoUrl: repo,
14326
- sha,
14327
- reference,
14328
- shouldScan,
14329
- polling
14330
- });
14331
- uploadReportSpinner.success({ text: "\u{1F4C1} Report uploaded successfully" });
14332
- const mobbSpinner = createSpinner5("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
14333
- const sendReportRes = await sendReport({
14334
- gqlClient,
14335
- spinner: mobbSpinner,
14336
- submitVulnerabilityReportVariables: {
14337
- fixReportId: reportUploadInfo.fixReportId,
14338
- repoUrl: z30.string().parse(repo),
14339
- reference,
14340
- projectId,
14341
- vulnerabilityReportFileName: shouldScan ? void 0 : REPORT_DEFAULT_FILE_NAME,
14342
- sha,
14343
- experimentalEnabled: !!experimentalEnabled,
14344
- pullRequest: params.pullRequest,
14345
- scanSource: _getScanSource(command, ci),
14346
- scanContext: ScanContext.BUGSY
14347
- },
14348
- polling
14349
- });
14350
- if (sendReportRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
14351
- mobbSpinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
14352
- throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
14353
- }
14354
- mobbSpinner.success({
14355
- text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Generating fixes..."
14356
- });
14357
- if (autoPr) {
14358
- await handleAutoPr({
14359
- gqlClient,
14360
- analysisId: reportUploadInfo.fixReportId,
14361
- commitDirectly,
14362
- prId: pullRequest,
14363
- createSpinner: createSpinner5,
14364
- createOnePr,
14365
- polling
14366
- });
14367
- }
14368
- await askToOpenAnalysis();
14369
- if (command === "review") {
14370
- await waitForAnaysisAndReviewPr({
14371
- repo,
14372
- githubActionToken,
14373
- analysisId: reportUploadInfo.fixReportId,
14374
- scanner,
14375
- gqlClient,
14376
- polling
14377
- });
14378
- }
14379
- return reportUploadInfo.fixReportId;
14380
- async function askToOpenAnalysis() {
14381
- if (!repoUploadInfo || !reportUploadInfo) {
14382
- throw new Error("uploadS3BucketInfo is null");
14383
- }
14384
- const reportUrl = getReportUrl({
14385
- organizationId,
14386
- projectId,
14387
- fixReportId: reportUploadInfo.fixReportId
14388
- });
14389
- !ci && console.log("You can access the analysis at: \n");
14390
- console.log(chalk6.bold(reportUrl));
14391
- !skipPrompts && await mobbAnalysisPrompt();
14392
- !ci && open3(reportUrl);
14393
- !ci && console.log(
14394
- chalk6.bgBlue("\n\n My work here is done for now, see you soon! \u{1F575}\uFE0F\u200D\u2642\uFE0F ")
14395
- );
14396
- }
14397
- async function handleScmIntegration(oldToken, scmAuthUrl2, repoUrl) {
14398
- const scmLibType = getCloudScmLibTypeFromUrl(repoUrl);
14399
- const scmName = scmLibType === "GITHUB" /* GITHUB */ ? "Github" : scmLibType === "GITLAB" /* GITLAB */ ? "Gitlab" : scmLibType === "ADO" /* ADO */ ? "Azure DevOps" : "";
14400
- const addScmIntegration = skipPrompts ? true : await scmIntegrationPrompt(scmName);
14401
- const scmSpinner = createSpinner5(
14402
- `\u{1F517} Waiting for ${scmName} integration...`
14403
- ).start();
14404
- if (!addScmIntegration) {
14405
- scmSpinner.error();
14406
- throw Error(`Could not reach ${scmName} repo`);
14407
- }
14408
- console.log(
14409
- `If the page does not open automatically, kindly access it through ${scmAuthUrl2}.`
14410
- );
14411
- await open3(scmAuthUrl2);
14412
- for (let i = 0; i < LOGIN_MAX_WAIT2 / LOGIN_CHECK_DELAY2; i++) {
14413
- const userInfo2 = await gqlClient.getUserInfo();
14414
- if (!userInfo2) {
14415
- throw new CliError2("User info not found");
14416
- }
14417
- const scmConfigs = getFromArraySafe(userInfo2.scmConfigs);
14418
- const tokenInfo2 = getScmConfig({
14419
- url: repoUrl,
14420
- scmConfigs,
14421
- brokerHosts: getBrokerHosts(
14422
- userInfo2.userOrganizationsAndUserOrganizationRoles
14423
- ),
14424
- includeOrgTokens: false
14425
- });
14426
- if (tokenInfo2.accessToken && tokenInfo2.accessToken !== oldToken) {
14427
- scmSpinner.success({ text: `\u{1F517} ${scmName} integration successful!` });
14428
- return tokenInfo2.accessToken;
14429
- }
14430
- scmSpinner.spin();
14431
- await sleep(LOGIN_CHECK_DELAY2);
14432
- }
14433
- scmSpinner.error({
14434
- text: `${scmName} login timeout error`
14435
- });
14436
- throw new CliError2(`${scmName} login timeout`);
14437
- }
14438
- async function uploadExistingRepo() {
14439
- if (!repoUploadInfo || !reportUploadInfo) {
14440
- throw new Error("uploadS3BucketInfo is null");
14441
- }
14442
- if (!srcPath) {
14443
- throw new Error("src path is required");
14444
- }
14445
- const shouldScan2 = !reportPath;
14446
- if (reportPath) {
14447
- const uploadReportSpinner2 = createSpinner5("\u{1F4C1} Uploading Report").start();
14448
- try {
14449
- await uploadFile({
14450
- file: reportPath,
14451
- url: reportUploadInfo.url,
14452
- uploadFields: JSON.parse(reportUploadInfo.uploadFieldsJSON),
14453
- uploadKey: reportUploadInfo.uploadKey
14454
- });
14455
- } catch (e) {
14456
- uploadReportSpinner2.error({ text: "\u{1F4C1} Report upload failed" });
14457
- throw e;
14458
- }
14459
- uploadReportSpinner2.success({
14460
- text: "\u{1F4C1} Uploading Report successful!"
14461
- });
14462
- }
14463
- let gitInfo2 = { success: false };
14464
- if (reportPath) {
14465
- const vulnFiles = await _digestReport({
14466
- gqlClient,
14467
- fixReportId: reportUploadInfo.fixReportId,
14468
- projectId,
14469
- command,
14470
- ci,
14471
- shouldScan: shouldScan2,
14472
- polling
14473
- });
14474
- const res = await _zipAndUploadRepo({
14475
- srcPath,
14476
- vulnFiles,
14477
- repoUploadInfo,
14478
- isIncludeAllFiles: false
14479
- });
14480
- gitInfo2 = res.gitInfo;
14481
- } else {
14482
- const res = await _zipAndUploadRepo({
14483
- srcPath,
14484
- vulnFiles: [],
14485
- repoUploadInfo,
14486
- isIncludeAllFiles: true
14487
- });
14488
- gitInfo2 = res.gitInfo;
14489
- await _digestReport({
14490
- gqlClient,
14491
- fixReportId: reportUploadInfo.fixReportId,
14492
- projectId,
14493
- command,
14494
- ci,
14495
- shouldScan: shouldScan2,
14496
- polling
14497
- });
14498
- }
14499
- const mobbSpinner2 = createSpinner5("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
14500
- try {
14501
- await sendReport({
14502
- gqlClient,
14503
- spinner: mobbSpinner2,
14504
- submitVulnerabilityReportVariables: {
14505
- fixReportId: reportUploadInfo.fixReportId,
14506
- projectId,
14507
- repoUrl: repo || gitInfo2.repoUrl || getTopLevelDirName(srcPath),
14508
- reference: ref || gitInfo2.reference || "no-branch",
14509
- sha: commitHash || gitInfo2.hash || "0123456789abcdef",
14510
- scanSource: _getScanSource(command, ci),
14511
- pullRequest: params.pullRequest,
14512
- experimentalEnabled: !!experimentalEnabled,
14513
- scanContext: ScanContext.BUGSY
14514
- },
14515
- polling
14516
- });
14517
- if (command === "review") {
14518
- await waitForAnaysisAndReviewPr({
14519
- repo,
14520
- githubActionToken,
14521
- analysisId: reportUploadInfo.fixReportId,
14522
- scanner,
14523
- gqlClient,
14524
- polling
14525
- });
14526
- }
14527
- } catch (e) {
14528
- mobbSpinner2.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
14529
- throw e;
14530
- }
14531
- mobbSpinner2.success({
14532
- text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Generating fixes..."
14533
- });
14534
- if (autoPr) {
14535
- await handleAutoPr({
14536
- gqlClient,
14537
- analysisId: reportUploadInfo.fixReportId,
14538
- commitDirectly,
14539
- prId: pullRequest,
14540
- createSpinner: createSpinner5,
14541
- createOnePr,
14542
- polling
14543
- });
14515
+ display: false
14516
+ });
14517
+ if (loginCode !== CHECKMARX_SUCCESS_CODE) {
14518
+ if (skipPrompts) {
14519
+ await throwCheckmarxConfigError();
14544
14520
  }
14545
- await askToOpenAnalysis();
14546
- return reportUploadInfo.fixReportId;
14521
+ await startCheckmarxConfigationPrompt();
14522
+ await validateCheckamxCredentials();
14547
14523
  }
14548
- }
14549
- async function _zipAndUploadRepo({
14550
- srcPath,
14551
- vulnFiles,
14552
- repoUploadInfo,
14553
- isIncludeAllFiles
14554
- }) {
14555
- const srcFileStatus = await fsPromises2.lstat(srcPath);
14556
- const zippingSpinner = createSpinner4("\u{1F4E6} Zipping repo").start();
14557
- let zipBuffer;
14558
- let gitInfo2 = { success: false };
14559
- if (srcFileStatus.isFile() && path9.extname(srcPath).toLowerCase() === ".fpr") {
14560
- zipBuffer = await repackFpr(srcPath);
14561
- } else {
14562
- gitInfo2 = await getGitInfo(srcPath);
14563
- zipBuffer = await pack(srcPath, vulnFiles, isIncludeAllFiles);
14524
+ const extension = path9.extname(reportPath);
14525
+ const filePath = path9.dirname(reportPath);
14526
+ const fileName = path9.basename(reportPath, extension);
14527
+ const checkmarxCommandArgs = getCheckmarxCommandArgs({
14528
+ repoPath: repositoryRoot,
14529
+ branch,
14530
+ filePath,
14531
+ fileName,
14532
+ projectName
14533
+ });
14534
+ console.log("\u280B \u{1F50D} Initiating Checkmarx Scan ");
14535
+ const { code: scanCode } = await forkCheckmarx(
14536
+ [...SCAN_COMMAND, ...checkmarxCommandArgs],
14537
+ {
14538
+ display: true
14539
+ }
14540
+ );
14541
+ if (scanCode !== CHECKMARX_SUCCESS_CODE) {
14542
+ createSpinner2("\u{1F50D} Something went wrong with the checkmarx scan").start().error();
14543
+ throw new CliError();
14564
14544
  }
14565
- zippingSpinner.success({ text: "\u{1F4E6} Zipping repo successful!" });
14566
- const uploadRepoSpinner = createSpinner4("\u{1F4C1} Uploading Repo").start();
14567
- try {
14568
- await uploadFile({
14569
- file: zipBuffer,
14570
- url: repoUploadInfo.url,
14571
- uploadFields: JSON.parse(repoUploadInfo.uploadFieldsJSON),
14572
- uploadKey: repoUploadInfo.uploadKey
14573
- });
14574
- } catch (e) {
14575
- uploadRepoSpinner.error({ text: "\u{1F4C1} Repo upload failed" });
14576
- throw e;
14545
+ await createSpinner2("\u{1F50D} Checkmarx Scan completed").start().success();
14546
+ return true;
14547
+ }
14548
+ async function throwCheckmarxConfigError() {
14549
+ await createSpinner2("\u{1F513} Checkmarx is not configued correctly").start().error();
14550
+ throw new CliError(
14551
+ `Checkmarx is not configued correctly
14552
+ you can configure it by using the ${chalk5.bold(
14553
+ "cx configure"
14554
+ )} command`
14555
+ );
14556
+ }
14557
+ async function validateCheckamxCredentials() {
14558
+ console.log(`
14559
+ Here's a suggestion for checkmarx configuation:
14560
+ ${chalk5.bold("AST Base URI:")} https://ast.checkmarx.net
14561
+ ${chalk5.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
14562
+ `);
14563
+ await forkCheckmarx(CONFIGURE_COMMAND, { display: true });
14564
+ const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
14565
+ display: false
14566
+ });
14567
+ if (loginCode !== CHECKMARX_SUCCESS_CODE) {
14568
+ const tryAgain = await tryCheckmarxConfiguarationAgain();
14569
+ if (!tryAgain) {
14570
+ await throwCheckmarxConfigError();
14571
+ }
14572
+ await validateCheckamxCredentials();
14573
+ return;
14577
14574
  }
14578
- uploadRepoSpinner.success({ text: "\u{1F4C1} Uploading Repo successful!" });
14579
- return { gitInfo: gitInfo2 };
14575
+ await createSpinner2("\u{1F513} Checkmarx configured successfully!").start().success();
14580
14576
  }
14581
- async function _digestReport({
14582
- gqlClient,
14583
- fixReportId,
14584
- projectId,
14585
- command,
14586
- ci,
14587
- repoUrl,
14588
- sha,
14589
- reference,
14590
- shouldScan,
14591
- polling
14592
- }) {
14593
- const digestSpinner = createSpinner4(
14594
- progressMassages.processingVulnerabilityReport
14595
- ).start();
14577
+
14578
+ // src/features/analysis/scanners/snyk.ts
14579
+ import { createRequire as createRequire2 } from "module";
14580
+ import chalk6 from "chalk";
14581
+ import Debug20 from "debug";
14582
+ import { createSpinner as createSpinner3 } from "nanospinner";
14583
+ import open2 from "open";
14584
+ var debug20 = Debug20("mobbdev:snyk");
14585
+ var moduleUrl2;
14586
+ if (typeof __filename !== "undefined") {
14587
+ moduleUrl2 = __filename;
14588
+ } else {
14596
14589
  try {
14597
- const { vulnerabilityReportId } = await gqlClient.digestVulnerabilityReport(
14598
- {
14599
- fixReportId,
14600
- projectId,
14601
- scanSource: _getScanSource(command, ci),
14602
- repoUrl,
14603
- sha,
14604
- reference,
14605
- shouldScan
14606
- }
14607
- );
14608
- const callbackStates = [
14609
- "Digested" /* Digested */,
14610
- "Finished" /* Finished */
14611
- ];
14612
- const callback = (_analysisId) => digestSpinner.update({
14613
- text: progressMassages.processingVulnerabilityReportSuccess
14614
- });
14615
- if (polling) {
14616
- debug19(
14617
- "[_digestReport] Using POLLING mode for analysis state updates (--polling flag enabled)"
14618
- );
14619
- console.log(
14620
- chalk6.cyan(
14621
- "\u{1F504} [Polling Mode] Using HTTP polling instead of WebSocket for status updates"
14622
- )
14623
- );
14624
- await gqlClient.pollForAnalysisState({
14625
- analysisId: fixReportId,
14626
- callback,
14627
- callbackStates,
14628
- timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
14629
- });
14590
+ const getImportMetaUrl = new Function("return import.meta.url");
14591
+ moduleUrl2 = getImportMetaUrl();
14592
+ } catch {
14593
+ const err = new Error();
14594
+ const stack = err.stack || "";
14595
+ const match = stack.match(/file:\/\/[^\s)]+/);
14596
+ if (match) {
14597
+ moduleUrl2 = match[0];
14630
14598
  } else {
14631
- debug19(
14632
- "[_digestReport] Using WEBSOCKET mode for analysis state updates (default)"
14633
- );
14634
- console.log(
14635
- chalk6.cyan(
14636
- "\u{1F50C} [WebSocket Mode] Using WebSocket subscription for status updates"
14637
- )
14638
- );
14639
- await gqlClient.subscribeToAnalysis({
14640
- subscribeToAnalysisParams: {
14641
- analysisId: fixReportId
14642
- },
14643
- callback,
14644
- callbackStates,
14645
- timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
14646
- });
14599
+ throw new Error("Unable to determine module URL in this environment");
14647
14600
  }
14648
- const vulnFiles = await gqlClient.getVulnerabilityReportPaths(
14649
- vulnerabilityReportId
14650
- );
14651
- digestSpinner.success({
14652
- text: progressMassages.processingVulnerabilityReportSuccess
14653
- });
14654
- return vulnFiles;
14655
- } catch (e) {
14656
- const errorMessage = e instanceof ReportDigestError ? e.getDisplayMessage() : ReportDigestError.defaultMessage;
14657
- digestSpinner.error({
14658
- text: errorMessage
14659
- });
14660
- throw e;
14661
14601
  }
14662
14602
  }
14663
- async function waitForAnaysisAndReviewPr({
14664
- repo,
14665
- githubActionToken,
14666
- analysisId,
14667
- scanner,
14668
- gqlClient,
14669
- polling
14670
- }) {
14671
- const params = z30.object({
14672
- repo: z30.string().url(),
14673
- githubActionToken: z30.string()
14674
- }).parse({ repo, githubActionToken });
14675
- const scm = await createScmLib(
14676
- {
14677
- url: params.repo,
14678
- accessToken: params.githubActionToken,
14679
- scmOrg: "",
14680
- scmType: "GITHUB" /* GITHUB */
14681
- },
14682
- {
14683
- propagateExceptions: true
14603
+ var costumeRequire2 = createRequire2(moduleUrl2);
14604
+ var SNYK_PATH = costumeRequire2.resolve("snyk/bin/snyk");
14605
+ var SNYK_ARTICLE_URL = "https://docs.snyk.io/scan-using-snyk/snyk-code/configure-snyk-code#enable-snyk-code";
14606
+ debug20("snyk executable path %s", SNYK_PATH);
14607
+ async function forkSnyk(args, { display }) {
14608
+ debug20("fork snyk with args %o %s", args, display);
14609
+ return createFork({ args, processPath: SNYK_PATH, name: "snyk" }, { display });
14610
+ }
14611
+ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
14612
+ debug20("get snyk report start %s %s", reportPath, repoRoot);
14613
+ const config2 = await forkSnyk(["config"], { display: false });
14614
+ const { message: configMessage } = config2;
14615
+ if (!configMessage.includes("api: ")) {
14616
+ const snykLoginSpinner = createSpinner3().start();
14617
+ if (!skipPrompts) {
14618
+ snykLoginSpinner.update({
14619
+ text: "\u{1F513} Login to Snyk is required, press any key to continue"
14620
+ });
14621
+ await keypress();
14684
14622
  }
14685
- );
14686
- const callback = (analysisId2) => {
14687
- return addFixCommentsForPr({
14688
- analysisId: analysisId2,
14689
- gqlClient,
14690
- scm,
14691
- scanner: z30.nativeEnum(SCANNERS).parse(scanner)
14692
- });
14693
- };
14694
- if (polling) {
14695
- debug19(
14696
- "[waitForAnaysisAndReviewPr] Using POLLING mode for analysis state updates"
14697
- );
14698
- console.log(
14699
- chalk6.cyan(
14700
- "\u{1F504} [Polling Mode] Waiting for analysis completion using HTTP polling"
14701
- )
14702
- );
14703
- await gqlClient.pollForAnalysisState({
14704
- analysisId,
14705
- callback,
14706
- callbackStates: ["Finished" /* Finished */]
14623
+ snykLoginSpinner.update({
14624
+ text: "\u{1F513} Waiting for Snyk login to complete"
14707
14625
  });
14708
- } else {
14709
- debug19(
14710
- "[waitForAnaysisAndReviewPr] Using WEBSOCKET mode for analysis state updates"
14711
- );
14626
+ debug20("no token in the config %s", config2);
14627
+ await forkSnyk(["auth"], { display: true });
14628
+ snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
14629
+ }
14630
+ const snykSpinner = createSpinner3("\u{1F50D} Scanning your repo with Snyk ").start();
14631
+ const { message: scanOutput } = await forkSnyk(
14632
+ ["code", "test", `--sarif-file-output=${reportPath}`, repoRoot],
14633
+ { display: true }
14634
+ );
14635
+ if (scanOutput.includes("Snyk Code is not supported for org")) {
14636
+ debug20("snyk code is not enabled %s", scanOutput);
14637
+ snykSpinner.error({ text: "\u{1F50D} Snyk configuration needed" });
14638
+ const answer = await snykArticlePrompt();
14639
+ debug20("answer %s", answer);
14640
+ if (answer) {
14641
+ debug20("opening the browser");
14642
+ await open2(SNYK_ARTICLE_URL);
14643
+ }
14712
14644
  console.log(
14713
- chalk6.cyan(
14714
- "\u{1F50C} [WebSocket Mode] Waiting for analysis completion using WebSocket"
14715
- )
14716
- );
14717
- await gqlClient.subscribeToAnalysis({
14718
- subscribeToAnalysisParams: {
14719
- analysisId
14720
- },
14721
- callback,
14722
- callbackStates: ["Finished" /* Finished */]
14723
- });
14645
+ chalk6.bgBlue(
14646
+ "\nPlease enable Snyk Code in your Snyk account and try again."
14647
+ )
14648
+ );
14649
+ throw Error("snyk is not enbabled");
14724
14650
  }
14651
+ snykSpinner.success({ text: "\u{1F50D} Snyk code scan completed" });
14652
+ return true;
14725
14653
  }
14726
14654
 
14727
- // src/commands/scan_skill_input.ts
14728
- import fs11 from "fs";
14729
- import path10 from "path";
14730
- import AdmZip2 from "adm-zip";
14731
- var LOCAL_SKILL_ZIP_DATA_URL_PREFIX = "data:application/zip;base64,";
14732
- var MAX_LOCAL_SKILL_ARCHIVE_BYTES = 2 * 1024 * 1024;
14733
- async function resolveSkillScanInput(skillInput) {
14734
- const resolvedPath = path10.resolve(skillInput);
14735
- if (!fs11.existsSync(resolvedPath)) {
14736
- return skillInput;
14655
+ // src/features/analysis/index.ts
14656
+ init_client_generates();
14657
+ var { CliError: CliError2, Spinner: Spinner2 } = utils_exports;
14658
+ function _getScanSource(command, ci) {
14659
+ if (command === "review") return "AUTO_FIXER" /* AutoFixer */;
14660
+ const envToCi = [
14661
+ ["GITLAB_CI", "CI_GITLAB" /* CiGitlab */],
14662
+ ["GITHUB_ACTIONS", "CI_GITHUB" /* CiGithub */],
14663
+ ["JENKINS_URL", "CI_JENKINS" /* CiJenkins */],
14664
+ ["CIRCLECI", "CI_CIRCLECI" /* CiCircleci */],
14665
+ ["TF_BUILD", "CI_AZURE" /* CiAzure */],
14666
+ ["bamboo_buildKey", "CI_BAMBOO" /* CiBamboo */]
14667
+ ];
14668
+ for (const [envKey, source] of envToCi) {
14669
+ if (env2[envKey]) {
14670
+ return source;
14671
+ }
14737
14672
  }
14738
- const stat = fs11.statSync(resolvedPath);
14739
- if (!stat.isDirectory()) {
14740
- throw new CliError(
14741
- "Local skill input must be a directory containing SKILL.md"
14742
- );
14673
+ if (ci) {
14674
+ return "CI_UNKNOWN" /* CiUnknown */;
14743
14675
  }
14744
- return packageLocalSkillDirectory(resolvedPath);
14676
+ return "CLI" /* Cli */;
14745
14677
  }
14746
- function packageLocalSkillDirectory(directoryPath) {
14747
- const zip = new AdmZip2();
14748
- const rootPath = path10.resolve(directoryPath);
14749
- const filePaths = listDirectoryFiles(rootPath);
14750
- let hasSkillMd = false;
14751
- for (const relativePath of filePaths) {
14752
- const fullPath = path10.join(rootPath, relativePath);
14753
- const normalizedFullPath = path10.resolve(fullPath);
14754
- if (normalizedFullPath !== rootPath && !normalizedFullPath.startsWith(rootPath + path10.sep)) {
14755
- continue;
14756
- }
14757
- const fileContent = fs11.readFileSync(normalizedFullPath);
14758
- zip.addFile(relativePath, fileContent);
14759
- if (path10.basename(relativePath).toLowerCase() === "skill.md") {
14760
- hasSkillMd = true;
14678
+ async function downloadRepo({
14679
+ repoUrl,
14680
+ authHeaders,
14681
+ downloadUrl,
14682
+ dirname,
14683
+ ci
14684
+ }) {
14685
+ const { createSpinner: createSpinner5 } = Spinner2({ ci });
14686
+ const repoSpinner = createSpinner5("\u{1F4BE} Downloading Repo").start();
14687
+ debug21("download repo %s %s %s", repoUrl, dirname);
14688
+ const zipFilePath = path10.join(dirname, "repo.zip");
14689
+ debug21("download URL: %s auth headers: %o", downloadUrl, authHeaders);
14690
+ const response = await fetch4(downloadUrl, {
14691
+ method: "GET",
14692
+ headers: {
14693
+ ...authHeaders
14761
14694
  }
14695
+ });
14696
+ if (!response.ok) {
14697
+ debug21("SCM zipball request failed %s %s", response.body, response.status);
14698
+ repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
14699
+ throw new Error(`Can't access ${chalk7.bold(repoUrl)}`);
14762
14700
  }
14763
- if (!hasSkillMd) {
14764
- throw new CliError("Local skill directory must contain a SKILL.md file");
14701
+ const fileWriterStream = fs10.createWriteStream(zipFilePath);
14702
+ if (!response.body) {
14703
+ throw new Error("Response body is empty");
14765
14704
  }
14766
- const zipBuffer = zip.toBuffer();
14767
- if (zipBuffer.length > MAX_LOCAL_SKILL_ARCHIVE_BYTES) {
14768
- throw new CliError(
14769
- `Local skill directory is too large to scan via CLI (${zipBuffer.length} bytes > ${MAX_LOCAL_SKILL_ARCHIVE_BYTES} bytes)`
14705
+ await pipeline(response.body, fileWriterStream);
14706
+ await extract(zipFilePath, { dir: dirname });
14707
+ const repoRoot = fs10.readdirSync(dirname, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name)[0];
14708
+ if (!repoRoot) {
14709
+ throw new Error("Repo root not found");
14710
+ }
14711
+ debug21("repo root %s", repoRoot);
14712
+ repoSpinner.success({ text: "\u{1F4BE} Repo downloaded successfully" });
14713
+ return path10.join(dirname, repoRoot);
14714
+ }
14715
+ var getReportUrl = ({
14716
+ organizationId,
14717
+ projectId,
14718
+ fixReportId
14719
+ }) => `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${fixReportId}`;
14720
+ var debug21 = Debug21("mobbdev:index");
14721
+ async function runAnalysis(params, options) {
14722
+ const tmpObj = tmp2.dirSync({
14723
+ unsafeCleanup: true
14724
+ });
14725
+ try {
14726
+ return await _scan(
14727
+ {
14728
+ ...params,
14729
+ dirname: tmpObj.name
14730
+ },
14731
+ options
14770
14732
  );
14733
+ } finally {
14734
+ tmpObj.removeCallback();
14771
14735
  }
14772
- return `${LOCAL_SKILL_ZIP_DATA_URL_PREFIX}${zipBuffer.toString("base64")}`;
14773
14736
  }
14774
- function listDirectoryFiles(rootPath) {
14775
- const files = [];
14776
- function walk(relativeDir) {
14777
- const absoluteDir = path10.join(rootPath, relativeDir);
14778
- const entries = fs11.readdirSync(absoluteDir, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
14779
- for (const entry of entries) {
14780
- const safeEntryName = path10.basename(entry.name).replace(/\0/g, "");
14781
- if (!safeEntryName) {
14782
- continue;
14783
- }
14784
- const relativePath = relativeDir ? path10.posix.join(relativeDir, safeEntryName) : safeEntryName;
14785
- const absolutePath = path10.join(rootPath, relativePath);
14786
- const normalizedAbsolutePath = path10.resolve(absolutePath);
14787
- if (normalizedAbsolutePath !== rootPath && !normalizedAbsolutePath.startsWith(rootPath + path10.sep)) {
14788
- continue;
14789
- }
14790
- if (entry.isSymbolicLink()) {
14791
- continue;
14792
- }
14793
- if (entry.isDirectory()) {
14794
- walk(relativePath);
14795
- continue;
14796
- }
14797
- if (entry.isFile()) {
14798
- files.push(relativePath);
14737
+ function _getUrlForScmType({
14738
+ scmLibType
14739
+ }) {
14740
+ const githubAuthUrl = `${WEB_APP_URL}/github-auth`;
14741
+ const gitlabAuthUrl = `${WEB_APP_URL}/gitlab-auth`;
14742
+ const adoAuthUrl = `${WEB_APP_URL}/ado-auth`;
14743
+ switch (scmLibType) {
14744
+ case "GITHUB" /* GITHUB */:
14745
+ return {
14746
+ authUrl: githubAuthUrl
14747
+ };
14748
+ case "GITLAB" /* GITLAB */:
14749
+ return {
14750
+ authUrl: gitlabAuthUrl
14751
+ };
14752
+ case "ADO" /* ADO */:
14753
+ return {
14754
+ authUrl: adoAuthUrl
14755
+ };
14756
+ default:
14757
+ return {
14758
+ authUrl: void 0
14759
+ };
14760
+ }
14761
+ }
14762
+ function getBrokerHosts(userOrgsAnUserOrgRoles) {
14763
+ const brokerHosts = [];
14764
+ if (!userOrgsAnUserOrgRoles) {
14765
+ return brokerHosts;
14766
+ }
14767
+ userOrgsAnUserOrgRoles.forEach((org) => {
14768
+ org?.organization?.brokerHosts.forEach((brokerHost) => {
14769
+ if (brokerHost) {
14770
+ brokerHosts.push(brokerHost);
14799
14771
  }
14800
- }
14772
+ });
14773
+ });
14774
+ return brokerHosts;
14775
+ }
14776
+ async function getScmTokenInfo(params) {
14777
+ const { gqlClient, repo } = params;
14778
+ const userInfo2 = await gqlClient.getUserInfo();
14779
+ if (!userInfo2) {
14780
+ throw new Error("userInfo is null");
14801
14781
  }
14802
- walk("");
14803
- return files;
14782
+ const scmConfigs = getFromArraySafe(userInfo2.scmConfigs);
14783
+ return getScmConfig({
14784
+ url: repo,
14785
+ scmConfigs,
14786
+ includeOrgTokens: false,
14787
+ brokerHosts: getBrokerHosts(
14788
+ userInfo2.userOrganizationsAndUserOrganizationRoles
14789
+ )
14790
+ });
14804
14791
  }
14805
-
14806
- // src/commands/index.ts
14807
- async function review(params, { skipPrompts = true } = {}) {
14792
+ async function getReport(params, { skipPrompts }) {
14808
14793
  const {
14809
- repo,
14810
- f: scanFile,
14811
- ref,
14812
- apiKey,
14813
- commitHash,
14814
- mobbProjectName,
14815
- pullRequest,
14816
- githubToken,
14817
14794
  scanner,
14818
- srcPath,
14819
- polling
14795
+ repoUrl,
14796
+ gqlClient,
14797
+ sha,
14798
+ dirname,
14799
+ reference,
14800
+ cxProjectName,
14801
+ ci
14820
14802
  } = params;
14821
- await runAnalysis(
14822
- {
14823
- repo,
14824
- scanFile,
14825
- ref,
14826
- apiKey,
14827
- ci: true,
14828
- commitHash,
14829
- experimentalEnabled: false,
14830
- mobbProjectName,
14831
- pullRequest,
14832
- githubToken,
14833
- scanner,
14834
- command: "review",
14835
- srcPath,
14836
- polling
14837
- },
14838
- { skipPrompts }
14839
- );
14840
- }
14841
- async function analyze({
14842
- repo,
14843
- f: scanFile,
14844
- ref,
14845
- apiKey,
14846
- ci,
14847
- commitHash,
14848
- srcPath,
14849
- mobbProjectName,
14850
- organizationId,
14851
- autoPr,
14852
- createOnePr,
14853
- commitDirectly,
14854
- pullRequest,
14855
- polling
14856
- }, { skipPrompts = false } = {}) {
14857
- !ci && await showWelcomeMessage(skipPrompts);
14858
- await runAnalysis(
14803
+ const tokenInfo = await getScmTokenInfo({ gqlClient, repo: repoUrl });
14804
+ const scm = await createScmLib(
14859
14805
  {
14860
- repo,
14861
- scanFile,
14862
- ref,
14863
- apiKey,
14864
- ci,
14865
- commitHash,
14866
- mobbProjectName,
14867
- srcPath,
14868
- organizationId,
14869
- command: "analyze",
14870
- autoPr,
14871
- commitDirectly,
14872
- pullRequest,
14873
- createOnePr,
14874
- polling
14806
+ url: repoUrl,
14807
+ accessToken: tokenInfo.accessToken,
14808
+ scmOrg: tokenInfo.scmOrg,
14809
+ scmType: tokenInfo.scmLibType
14875
14810
  },
14876
- { skipPrompts }
14811
+ { propagateExceptions: true }
14877
14812
  );
14813
+ const downloadUrl = await scm.getDownloadUrl(sha);
14814
+ const repositoryRoot = await downloadRepo({
14815
+ repoUrl,
14816
+ dirname,
14817
+ ci,
14818
+ authHeaders: scm.getAuthHeaders(),
14819
+ downloadUrl
14820
+ });
14821
+ const reportPath = path10.join(dirname, REPORT_DEFAULT_FILE_NAME);
14822
+ switch (scanner) {
14823
+ case "snyk":
14824
+ await getSnykReport(reportPath, repositoryRoot, { skipPrompts });
14825
+ break;
14826
+ case "checkmarx":
14827
+ if (!cxProjectName) {
14828
+ throw new Error("cxProjectName is required for checkmarx scanner");
14829
+ }
14830
+ await getCheckmarxReport(
14831
+ {
14832
+ reportPath,
14833
+ repositoryRoot,
14834
+ branch: reference,
14835
+ projectName: cxProjectName
14836
+ },
14837
+ { skipPrompts }
14838
+ );
14839
+ break;
14840
+ }
14841
+ return reportPath;
14878
14842
  }
14879
- async function addScmToken(addScmTokenOptions) {
14880
- const { apiKey, token, organization, scmType, url, refreshToken, ci } = addScmTokenOptions;
14843
+ async function _scan(params, { skipPrompts = false } = {}) {
14844
+ const {
14845
+ dirname,
14846
+ repo,
14847
+ scanFile,
14848
+ apiKey,
14849
+ ci,
14850
+ srcPath,
14851
+ commitHash,
14852
+ ref,
14853
+ experimentalEnabled,
14854
+ scanner,
14855
+ cxProjectName,
14856
+ mobbProjectName,
14857
+ githubToken: githubActionToken,
14858
+ command,
14859
+ organizationId: userOrganizationId,
14860
+ autoPr,
14861
+ createOnePr,
14862
+ commitDirectly,
14863
+ pullRequest,
14864
+ polling
14865
+ } = params;
14866
+ debug21("start %s %s", dirname, repo);
14867
+ const { createSpinner: createSpinner5 } = Spinner2({ ci });
14868
+ skipPrompts = skipPrompts || ci;
14881
14869
  const gqlClient = await getAuthenticatedGQLClient({
14882
14870
  inputApiKey: apiKey,
14883
- isSkipPrompts: ci
14871
+ isSkipPrompts: skipPrompts
14884
14872
  });
14885
- if (!scmType) {
14886
- throw new CliError(errorMessages.invalidScmType);
14873
+ if (!mobbProjectName) {
14874
+ throw new Error("mobbProjectName is required");
14887
14875
  }
14888
- const resp = await gqlClient.updateScmToken({
14889
- scmType,
14890
- url,
14891
- token,
14892
- org: organization,
14893
- refreshToken
14876
+ const { projectId, organizationId } = await gqlClient.getLastOrgAndNamedProject({
14877
+ projectName: mobbProjectName,
14878
+ userDefinedOrganizationId: userOrganizationId
14894
14879
  });
14895
- if (resp.updateScmToken?.__typename === "RepoUnreachableError") {
14896
- throw new CliError(
14897
- "Mobb could not reach the repository. Please try again. If Mobb is connected through a broker, please make sure the broker is connected."
14898
- );
14899
- } else if (resp.updateScmToken?.__typename === "BadScmCredentials") {
14900
- throw new CliError("Invalid SCM credentials. Please try again.");
14901
- } else if (resp.updateScmToken?.__typename === "ScmAccessTokenUpdateSuccess") {
14902
- console.log("Token added successfully");
14903
- } else {
14904
- throw new CliError("Unexpected error, failed to add token");
14880
+ const {
14881
+ uploadS3BucketInfo: { repoUploadInfo, reportUploadInfo }
14882
+ } = await gqlClient.uploadS3BucketInfo();
14883
+ if (!reportUploadInfo || !repoUploadInfo) {
14884
+ throw new Error("uploadS3BucketInfo is null");
14905
14885
  }
14906
- }
14907
- async function scan(scanOptions, { skipPrompts = false } = {}) {
14908
- const { scanner, ci } = scanOptions;
14909
- !ci && await showWelcomeMessage(skipPrompts);
14910
- const selectedScanner = scanner || await choseScanner();
14911
- if (selectedScanner !== SCANNERS.Checkmarx && selectedScanner !== SCANNERS.Snyk) {
14912
- throw new CliError(
14913
- "Vulnerability scanning via Bugsy is available only with Snyk and Checkmarx at the moment. Additional scanners will follow soon."
14914
- );
14886
+ let reportPath = scanFile;
14887
+ if (srcPath) {
14888
+ return await uploadExistingRepo();
14915
14889
  }
14916
- selectedScanner === SCANNERS.Checkmarx && validateCheckmarxInstallation();
14917
- if (selectedScanner === SCANNERS.Checkmarx && !scanOptions.cxProjectName) {
14918
- throw new CliError(errorMessages.missingCxProjectName);
14890
+ if (!repo) {
14891
+ throw new Error("repo is required in case srcPath is not provided");
14919
14892
  }
14920
- await runAnalysis(
14921
- { ...scanOptions, scanner: selectedScanner, command: "scan" },
14922
- { skipPrompts }
14923
- );
14924
- }
14925
- async function showWelcomeMessage(skipPrompts = false) {
14926
- console.log(mobbAscii);
14927
- const welcome = chalkAnimation.rainbow("\n Welcome to Bugsy\n");
14928
- skipPrompts ? await sleep(100) : await sleep(2e3);
14929
- welcome.stop();
14930
- }
14931
- var VERDICT_COLORS = {
14932
- BENIGN: "green",
14933
- WARNING: "yellow",
14934
- SUSPICIOUS: "magenta",
14935
- MALICIOUS: "red"
14936
- };
14937
- var SEVERITY_COLORS = {
14938
- CRITICAL: "red",
14939
- HIGH: "magenta",
14940
- MEDIUM: "yellow",
14941
- LOW: "cyan"
14942
- };
14943
- async function scanSkill(options) {
14944
- const { url, apiKey, ci } = options;
14945
- const gqlClient = await getAuthenticatedGQLClient({
14946
- inputApiKey: apiKey,
14947
- isSkipPrompts: ci
14893
+ const tokenInfo = await getScmTokenInfo({ gqlClient, repo });
14894
+ const validateRes = await gqlClient.validateRepoUrl({ repoUrl: repo });
14895
+ const isRepoAvailable = validateRes.validateRepoUrl?.__typename === "RepoValidationSuccess";
14896
+ const cloudScmLibType = getCloudScmLibTypeFromUrl(repo);
14897
+ const { authUrl: scmAuthUrl } = _getUrlForScmType({
14898
+ scmLibType: cloudScmLibType
14948
14899
  });
14949
- console.log(chalk7.dim(`Scanning skill: ${url}`));
14950
- console.log();
14951
- const skillUrl = await resolveSkillScanInput(url);
14952
- const result = await gqlClient.scanSkill({ skillUrl });
14953
- const scan2 = result.scanSkill;
14954
- const verdictColor = VERDICT_COLORS[scan2.verdict] ?? "white";
14955
- console.log(
14956
- chalk7.bold(`Verdict: `) + chalk7[verdictColor](
14957
- scan2.verdict
14958
- )
14959
- );
14960
- console.log(`Skill: ${scan2.skillName}`);
14961
- if (scan2.skillVersion) {
14962
- console.log(`Version: ${scan2.skillVersion}`);
14963
- }
14964
- console.log(`Hash: ${scan2.skillHash ?? "N/A"}`);
14965
- console.log(`Findings: ${scan2.findingsCount}`);
14966
- console.log(`Duration: ${scan2.scanDurationMs}ms`);
14967
- if (scan2.cached) {
14968
- console.log(chalk7.dim("(cached result)"));
14969
- }
14970
- console.log();
14971
- if (scan2.findings.length > 0) {
14972
- console.log(chalk7.bold("Findings:"));
14973
- console.log();
14974
- for (const f of scan2.findings) {
14975
- const sevColor = SEVERITY_COLORS[f.severity] ?? "white";
14976
- const location = [f.filePath, f.lineNumber].filter(Boolean).join(":");
14977
- console.log(
14978
- ` ${chalk7[sevColor](f.severity)} [${f.layer}] ${f.category}${f.ruleId ? ` (${f.ruleId})` : ""}`
14979
- );
14980
- if (location) {
14981
- console.log(` ${chalk7.dim(location)}`);
14982
- }
14983
- console.log(` ${f.explanation}`);
14984
- if (f.evidence) {
14985
- console.log(
14986
- ` ${String(chalk7.dim("Evidence: " + f.evidence.slice(0, 120))).replace(/\n|\r/g, "")}`
14900
+ if (!isRepoAvailable) {
14901
+ if (ci || !cloudScmLibType || !scmAuthUrl) {
14902
+ const errorMessage = scmAuthUrl ? `Cannot access repo ${repo}. Make sure that the repo is accessible and the SCM token configured on Mobb is correct.` : `Cannot access repo ${repo} with the provided token, please visit ${scmAuthUrl} to refresh your source control management system token`;
14903
+ throw new Error(errorMessage);
14904
+ }
14905
+ if (cloudScmLibType && scmAuthUrl) {
14906
+ await handleScmIntegration(tokenInfo.accessToken, scmAuthUrl, repo);
14907
+ const repoValidationResponse = await gqlClient.validateRepoUrl({
14908
+ repoUrl: repo
14909
+ });
14910
+ const isRepoAvailable2 = repoValidationResponse.validateRepoUrl?.__typename === "RepoValidationSuccess";
14911
+ if (!isRepoAvailable2) {
14912
+ throw new Error(
14913
+ `Cannot access repo ${repo} with the provided credentials: ${repoValidationResponse.validateRepoUrl?.__typename}`
14987
14914
  );
14988
14915
  }
14989
- console.log();
14990
14916
  }
14991
14917
  }
14992
- if (scan2.summary) {
14993
- console.log(chalk7.bold("Analysis:"));
14994
- console.log(` ${scan2.summary}`);
14995
- console.log();
14996
- }
14997
- if (scan2.verdict === "MALICIOUS" || scan2.verdict === "SUSPICIOUS") {
14998
- process.exit(2);
14999
- }
15000
- }
15001
-
15002
- // src/args/validation.ts
15003
- import chalk8 from "chalk";
15004
- import path11 from "path";
15005
- import { z as z31 } from "zod";
15006
- function throwRepoUrlErrorMessage({
15007
- error,
15008
- repoUrl,
15009
- command
15010
- }) {
15011
- const errorMessage = error.issues[error.issues.length - 1]?.message;
15012
- const formattedErrorMessage = `
15013
- Error: ${chalk8.bold(
15014
- repoUrl
15015
- )} is ${errorMessage}
15016
- Example:
15017
- mobbdev ${command} -r ${chalk8.bold(
15018
- "https://github.com/WebGoat/WebGoat"
15019
- )}`;
15020
- throw new CliError(formattedErrorMessage);
15021
- }
15022
- var UrlZ = z31.string({
15023
- invalid_type_error: `is not a valid ${Object.values(ScmType).join("/ ")} URL`
15024
- });
15025
- function validateOrganizationId(organizationId) {
15026
- const orgIdValidation = z31.string().uuid().nullish().safeParse(organizationId);
15027
- if (!orgIdValidation.success) {
15028
- throw new CliError(`organizationId: ${organizationId} is not a valid UUID`);
15029
- }
15030
- }
15031
- function validateRepoUrl(args) {
15032
- const repoSafeParseResult = UrlZ.safeParse(args.repo);
15033
- const { success } = repoSafeParseResult;
15034
- const [command] = args._;
15035
- if (!command) {
15036
- throw new CliError("Command not found");
14918
+ const revalidateRes = await gqlClient.validateRepoUrl({ repoUrl: repo });
14919
+ if (revalidateRes.validateRepoUrl?.__typename !== "RepoValidationSuccess") {
14920
+ throw new Error(
14921
+ `could not reach repo ${repo}: ${revalidateRes.validateRepoUrl?.__typename}`
14922
+ );
15037
14923
  }
15038
- if (!success) {
15039
- throwRepoUrlErrorMessage({
15040
- error: repoSafeParseResult.error,
15041
- repoUrl: args.repo,
15042
- command
15043
- });
14924
+ const reference = ref ?? revalidateRes.validateRepoUrl.defaultBranch;
14925
+ const getReferenceDataRes = await gqlClient.getReferenceData({
14926
+ reference,
14927
+ repoUrl: repo
14928
+ });
14929
+ if (getReferenceDataRes.gitReference?.__typename !== "GitReferenceData") {
14930
+ throw new Error(
14931
+ `Could not get reference data for ${reference}: ${getReferenceDataRes.gitReference?.__typename}`
14932
+ );
15044
14933
  }
15045
- }
15046
- var supportExtensions = [".json", ".xml", ".fpr", ".sarif", ".zip"];
15047
- function validateReportFileFormat(reportFile) {
15048
- if (!supportExtensions.includes(path11.extname(reportFile))) {
15049
- throw new CliError(
15050
- `
15051
- ${chalk8.bold(
15052
- reportFile
15053
- )} is not a supported file extension. Supported extensions are: ${chalk8.bold(
15054
- supportExtensions.join(", ")
15055
- )}
15056
- `
14934
+ const { sha } = getReferenceDataRes.gitReference;
14935
+ debug21("project id %s", projectId);
14936
+ debug21("default branch %s", reference);
14937
+ if (command === "scan") {
14938
+ reportPath = await getReport(
14939
+ {
14940
+ scanner: SupportedScannersZ.parse(scanner),
14941
+ repoUrl: repo,
14942
+ sha,
14943
+ gqlClient,
14944
+ cxProjectName,
14945
+ dirname,
14946
+ reference,
14947
+ ci
14948
+ },
14949
+ { skipPrompts }
15057
14950
  );
15058
14951
  }
15059
- }
15060
-
15061
- // src/args/commands/analyze.ts
15062
- function analyzeBuilder(yargs2) {
15063
- return yargs2.option("f", {
15064
- alias: "scan-file",
15065
- type: "string",
15066
- describe: chalk9.bold(
15067
- "Select the vulnerability report to analyze (Checkmarx, Snyk, Fortify, CodeQL, Sonarqube, Semgrep, Datadog)"
15068
- )
15069
- }).option("repo", repoOption).option("p", {
15070
- alias: "src-path",
15071
- describe: chalk9.bold(
15072
- "Path to the repository folder with the source code; alternatively, you can specify the Fortify FPR file to extract source code out of it"
15073
- ),
15074
- type: "string"
15075
- }).option("ref", refOption).option("ch", {
15076
- alias: "commit-hash",
15077
- describe: chalk9.bold("Hash of the commit"),
15078
- type: "string"
15079
- }).option("mobb-project-name", mobbProjectNameOption).option("y", yesOption).option("ci", ciOption).option("org", organizationIdOptions).option("api-key", apiKeyOption).option("commit-hash", commitHashOption).option("auto-pr", autoPrOption).option("create-one-pr", createOnePrOption).option("commit-directly", commitDirectlyOption).option("pull-request", {
15080
- alias: ["pr", "pr-number", "pr-id"],
15081
- describe: chalk9.bold("Number of the pull request"),
15082
- type: "number",
15083
- demandOption: false
15084
- }).option("polling", pollingOption).example(
15085
- "npx mobbdev@latest analyze -r https://github.com/WebGoat/WebGoat -f <your_vulnerability_report_path>",
15086
- "analyze an existing repository"
15087
- ).help();
15088
- }
15089
- function validateAnalyzeOptions(argv) {
15090
- if (argv.f && !fs12.existsSync(argv.f)) {
15091
- throw new CliError(`
15092
- Can't access ${chalk9.bold(argv.f)}`);
14952
+ const shouldScan = !reportPath;
14953
+ const uploadReportSpinner = createSpinner5("\u{1F4C1} Uploading Report").start();
14954
+ try {
14955
+ if (reportPath) {
14956
+ await uploadFile({
14957
+ file: reportPath,
14958
+ url: reportUploadInfo.url,
14959
+ uploadFields: JSON.parse(reportUploadInfo.uploadFieldsJSON),
14960
+ uploadKey: reportUploadInfo.uploadKey
14961
+ });
14962
+ }
14963
+ } catch (e) {
14964
+ uploadReportSpinner.error({ text: "\u{1F4C1} Report upload failed" });
14965
+ throw e;
15093
14966
  }
15094
- validateOrganizationId(argv.organizationId);
15095
- if (!argv.srcPath && !argv.repo) {
15096
- throw new CliError("You must supply either --src-path or --repo");
14967
+ await _digestReport({
14968
+ gqlClient,
14969
+ fixReportId: reportUploadInfo.fixReportId,
14970
+ projectId,
14971
+ command,
14972
+ ci,
14973
+ repoUrl: repo,
14974
+ sha,
14975
+ reference,
14976
+ shouldScan,
14977
+ polling
14978
+ });
14979
+ uploadReportSpinner.success({ text: "\u{1F4C1} Report uploaded successfully" });
14980
+ const mobbSpinner = createSpinner5("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
14981
+ const sendReportRes = await sendReport({
14982
+ gqlClient,
14983
+ spinner: mobbSpinner,
14984
+ submitVulnerabilityReportVariables: {
14985
+ fixReportId: reportUploadInfo.fixReportId,
14986
+ repoUrl: z31.string().parse(repo),
14987
+ reference,
14988
+ projectId,
14989
+ vulnerabilityReportFileName: shouldScan ? void 0 : REPORT_DEFAULT_FILE_NAME,
14990
+ sha,
14991
+ experimentalEnabled: !!experimentalEnabled,
14992
+ pullRequest: params.pullRequest,
14993
+ scanSource: _getScanSource(command, ci),
14994
+ scanContext: ScanContext.BUGSY
14995
+ },
14996
+ polling
14997
+ });
14998
+ if (sendReportRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
14999
+ mobbSpinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
15000
+ throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
15097
15001
  }
15098
- if (!argv.srcPath && argv.repo) {
15099
- validateRepoUrl(argv);
15002
+ mobbSpinner.success({
15003
+ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Generating fixes..."
15004
+ });
15005
+ if (autoPr) {
15006
+ await handleAutoPr({
15007
+ gqlClient,
15008
+ analysisId: reportUploadInfo.fixReportId,
15009
+ commitDirectly,
15010
+ prId: pullRequest,
15011
+ createSpinner: createSpinner5,
15012
+ createOnePr,
15013
+ polling
15014
+ });
15100
15015
  }
15101
- if (argv.ci && !argv.apiKey) {
15102
- throw new CliError("--ci flag requires --api-key to be provided as well");
15016
+ await askToOpenAnalysis();
15017
+ if (command === "review") {
15018
+ await waitForAnaysisAndReviewPr({
15019
+ repo,
15020
+ githubActionToken,
15021
+ analysisId: reportUploadInfo.fixReportId,
15022
+ scanner,
15023
+ gqlClient,
15024
+ polling
15025
+ });
15103
15026
  }
15104
- if (argv.commitDirectly && !argv["auto-pr"]) {
15105
- throw new CliError(
15106
- "--commit-directly flag requires --auto-pr to be provided as well"
15027
+ return reportUploadInfo.fixReportId;
15028
+ async function askToOpenAnalysis() {
15029
+ if (!repoUploadInfo || !reportUploadInfo) {
15030
+ throw new Error("uploadS3BucketInfo is null");
15031
+ }
15032
+ const reportUrl = getReportUrl({
15033
+ organizationId,
15034
+ projectId,
15035
+ fixReportId: reportUploadInfo.fixReportId
15036
+ });
15037
+ !ci && console.log("You can access the analysis at: \n");
15038
+ console.log(chalk7.bold(reportUrl));
15039
+ !skipPrompts && await mobbAnalysisPrompt();
15040
+ !ci && open3(reportUrl);
15041
+ !ci && console.log(
15042
+ chalk7.bgBlue("\n\n My work here is done for now, see you soon! \u{1F575}\uFE0F\u200D\u2642\uFE0F ")
15107
15043
  );
15108
15044
  }
15109
- if (argv["create-one-pr"] && !argv["auto-pr"]) {
15110
- throw new CliError(
15111
- "--create-one-pr flag requires --auto-pr to be provided as well"
15045
+ async function handleScmIntegration(oldToken, scmAuthUrl2, repoUrl) {
15046
+ const scmLibType = getCloudScmLibTypeFromUrl(repoUrl);
15047
+ const scmName = scmLibType === "GITHUB" /* GITHUB */ ? "Github" : scmLibType === "GITLAB" /* GITLAB */ ? "Gitlab" : scmLibType === "ADO" /* ADO */ ? "Azure DevOps" : "";
15048
+ const addScmIntegration = skipPrompts ? true : await scmIntegrationPrompt(scmName);
15049
+ const scmSpinner = createSpinner5(
15050
+ `\u{1F517} Waiting for ${scmName} integration...`
15051
+ ).start();
15052
+ if (!addScmIntegration) {
15053
+ scmSpinner.error();
15054
+ throw Error(`Could not reach ${scmName} repo`);
15055
+ }
15056
+ console.log(
15057
+ `If the page does not open automatically, kindly access it through ${scmAuthUrl2}.`
15112
15058
  );
15059
+ await open3(scmAuthUrl2);
15060
+ for (let i = 0; i < LOGIN_MAX_WAIT2 / LOGIN_CHECK_DELAY2; i++) {
15061
+ const userInfo2 = await gqlClient.getUserInfo();
15062
+ if (!userInfo2) {
15063
+ throw new CliError2("User info not found");
15064
+ }
15065
+ const scmConfigs = getFromArraySafe(userInfo2.scmConfigs);
15066
+ const tokenInfo2 = getScmConfig({
15067
+ url: repoUrl,
15068
+ scmConfigs,
15069
+ brokerHosts: getBrokerHosts(
15070
+ userInfo2.userOrganizationsAndUserOrganizationRoles
15071
+ ),
15072
+ includeOrgTokens: false
15073
+ });
15074
+ if (tokenInfo2.accessToken && tokenInfo2.accessToken !== oldToken) {
15075
+ scmSpinner.success({ text: `\u{1F517} ${scmName} integration successful!` });
15076
+ return tokenInfo2.accessToken;
15077
+ }
15078
+ scmSpinner.spin();
15079
+ await sleep(LOGIN_CHECK_DELAY2);
15080
+ }
15081
+ scmSpinner.error({
15082
+ text: `${scmName} login timeout error`
15083
+ });
15084
+ throw new CliError2(`${scmName} login timeout`);
15113
15085
  }
15114
- if (argv["create-one-pr"] && argv.commitDirectly) {
15115
- throw new CliError(
15116
- "--create-one-pr and --commit-directly cannot be provided at the same time"
15117
- );
15086
+ async function uploadExistingRepo() {
15087
+ if (!repoUploadInfo || !reportUploadInfo) {
15088
+ throw new Error("uploadS3BucketInfo is null");
15089
+ }
15090
+ if (!srcPath) {
15091
+ throw new Error("src path is required");
15092
+ }
15093
+ const shouldScan2 = !reportPath;
15094
+ if (reportPath) {
15095
+ const uploadReportSpinner2 = createSpinner5("\u{1F4C1} Uploading Report").start();
15096
+ try {
15097
+ await uploadFile({
15098
+ file: reportPath,
15099
+ url: reportUploadInfo.url,
15100
+ uploadFields: JSON.parse(reportUploadInfo.uploadFieldsJSON),
15101
+ uploadKey: reportUploadInfo.uploadKey
15102
+ });
15103
+ } catch (e) {
15104
+ uploadReportSpinner2.error({ text: "\u{1F4C1} Report upload failed" });
15105
+ throw e;
15106
+ }
15107
+ uploadReportSpinner2.success({
15108
+ text: "\u{1F4C1} Uploading Report successful!"
15109
+ });
15110
+ }
15111
+ let gitInfo2 = { success: false };
15112
+ if (reportPath) {
15113
+ const vulnFiles = await _digestReport({
15114
+ gqlClient,
15115
+ fixReportId: reportUploadInfo.fixReportId,
15116
+ projectId,
15117
+ command,
15118
+ ci,
15119
+ shouldScan: shouldScan2,
15120
+ polling
15121
+ });
15122
+ const res = await _zipAndUploadRepo({
15123
+ srcPath,
15124
+ vulnFiles,
15125
+ repoUploadInfo,
15126
+ isIncludeAllFiles: false
15127
+ });
15128
+ gitInfo2 = res.gitInfo;
15129
+ } else {
15130
+ const res = await _zipAndUploadRepo({
15131
+ srcPath,
15132
+ vulnFiles: [],
15133
+ repoUploadInfo,
15134
+ isIncludeAllFiles: true
15135
+ });
15136
+ gitInfo2 = res.gitInfo;
15137
+ await _digestReport({
15138
+ gqlClient,
15139
+ fixReportId: reportUploadInfo.fixReportId,
15140
+ projectId,
15141
+ command,
15142
+ ci,
15143
+ shouldScan: shouldScan2,
15144
+ polling
15145
+ });
15146
+ }
15147
+ const mobbSpinner2 = createSpinner5("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
15148
+ try {
15149
+ await sendReport({
15150
+ gqlClient,
15151
+ spinner: mobbSpinner2,
15152
+ submitVulnerabilityReportVariables: {
15153
+ fixReportId: reportUploadInfo.fixReportId,
15154
+ projectId,
15155
+ repoUrl: repo || gitInfo2.repoUrl || getTopLevelDirName(srcPath),
15156
+ reference: ref || gitInfo2.reference || "no-branch",
15157
+ sha: commitHash || gitInfo2.hash || "0123456789abcdef",
15158
+ scanSource: _getScanSource(command, ci),
15159
+ pullRequest: params.pullRequest,
15160
+ experimentalEnabled: !!experimentalEnabled,
15161
+ scanContext: ScanContext.BUGSY
15162
+ },
15163
+ polling
15164
+ });
15165
+ if (command === "review") {
15166
+ await waitForAnaysisAndReviewPr({
15167
+ repo,
15168
+ githubActionToken,
15169
+ analysisId: reportUploadInfo.fixReportId,
15170
+ scanner,
15171
+ gqlClient,
15172
+ polling
15173
+ });
15174
+ }
15175
+ } catch (e) {
15176
+ mobbSpinner2.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
15177
+ throw e;
15178
+ }
15179
+ mobbSpinner2.success({
15180
+ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Generating fixes..."
15181
+ });
15182
+ if (autoPr) {
15183
+ await handleAutoPr({
15184
+ gqlClient,
15185
+ analysisId: reportUploadInfo.fixReportId,
15186
+ commitDirectly,
15187
+ prId: pullRequest,
15188
+ createSpinner: createSpinner5,
15189
+ createOnePr,
15190
+ polling
15191
+ });
15192
+ }
15193
+ await askToOpenAnalysis();
15194
+ return reportUploadInfo.fixReportId;
15118
15195
  }
15119
- if (argv.pullRequest && !argv.commitDirectly) {
15120
- throw new CliError(
15121
- "--pull-request flag requires --commit-directly to be provided as well"
15122
- );
15196
+ }
15197
+ async function _zipAndUploadRepo({
15198
+ srcPath,
15199
+ vulnFiles,
15200
+ repoUploadInfo,
15201
+ isIncludeAllFiles
15202
+ }) {
15203
+ const srcFileStatus = await fsPromises3.lstat(srcPath);
15204
+ const zippingSpinner = createSpinner4("\u{1F4E6} Zipping repo").start();
15205
+ let zipBuffer;
15206
+ let gitInfo2 = { success: false };
15207
+ if (srcFileStatus.isFile() && path10.extname(srcPath).toLowerCase() === ".fpr") {
15208
+ zipBuffer = await repackFpr(srcPath);
15209
+ } else {
15210
+ gitInfo2 = await getGitInfo(srcPath);
15211
+ zipBuffer = await pack(srcPath, vulnFiles, isIncludeAllFiles);
15123
15212
  }
15124
- if (argv.f) {
15125
- validateReportFileFormat(argv.f);
15213
+ zippingSpinner.success({ text: "\u{1F4E6} Zipping repo successful!" });
15214
+ const uploadRepoSpinner = createSpinner4("\u{1F4C1} Uploading Repo").start();
15215
+ try {
15216
+ await uploadFile({
15217
+ file: zipBuffer,
15218
+ url: repoUploadInfo.url,
15219
+ uploadFields: JSON.parse(repoUploadInfo.uploadFieldsJSON),
15220
+ uploadKey: repoUploadInfo.uploadKey
15221
+ });
15222
+ } catch (e) {
15223
+ uploadRepoSpinner.error({ text: "\u{1F4C1} Repo upload failed" });
15224
+ throw e;
15126
15225
  }
15226
+ uploadRepoSpinner.success({ text: "\u{1F4C1} Uploading Repo successful!" });
15227
+ return { gitInfo: gitInfo2 };
15127
15228
  }
15128
- async function analyzeHandler(args) {
15129
- validateAnalyzeOptions(args);
15130
- await analyze(args, { skipPrompts: args.yes });
15131
- }
15132
-
15133
- // src/features/claude_code/data_collector.ts
15134
- import { z as z33 } from "zod";
15135
-
15136
- // src/args/commands/upload_ai_blame.ts
15137
- import fsPromises3 from "fs/promises";
15138
- import * as os3 from "os";
15139
- import path12 from "path";
15140
- import chalk10 from "chalk";
15141
- import { withFile } from "tmp-promise";
15142
- import z32 from "zod";
15143
- init_client_generates();
15144
- init_GitService();
15145
- init_urlParser2();
15146
-
15147
- // src/utils/computerName.ts
15148
- import { execSync } from "child_process";
15149
- import os2 from "os";
15150
- var STABLE_COMPUTER_NAME_CONFIG_KEY = "stableComputerName";
15151
- var HOSTNAME_SUFFIXES = [
15152
- // Cloud providers (must be first - most specific)
15153
- ".ec2.internal",
15154
- ".compute.internal",
15155
- ".cloudapp.net",
15156
- // mDNS/Bonjour
15157
- ".local",
15158
- ".localhost",
15159
- ".localdomain",
15160
- // Home networks
15161
- ".lan",
15162
- ".home",
15163
- ".homelan",
15164
- // Corporate networks
15165
- ".corp",
15166
- ".internal",
15167
- ".intranet",
15168
- ".domain",
15169
- ".work",
15170
- ".office",
15171
- // Container environments
15172
- ".docker",
15173
- ".kubernetes",
15174
- ".k8s"
15175
- ];
15176
- function getOsSpecificComputerName() {
15177
- const platform2 = os2.platform();
15229
+ async function _digestReport({
15230
+ gqlClient,
15231
+ fixReportId,
15232
+ projectId,
15233
+ command,
15234
+ ci,
15235
+ repoUrl,
15236
+ sha,
15237
+ reference,
15238
+ shouldScan,
15239
+ polling
15240
+ }) {
15241
+ const digestSpinner = createSpinner4(
15242
+ progressMassages.processingVulnerabilityReport
15243
+ ).start();
15178
15244
  try {
15179
- if (platform2 === "darwin") {
15180
- const result = execSync("scutil --get LocalHostName", {
15181
- encoding: "utf-8",
15182
- timeout: 1e3
15245
+ const { vulnerabilityReportId } = await gqlClient.digestVulnerabilityReport(
15246
+ {
15247
+ fixReportId,
15248
+ projectId,
15249
+ scanSource: _getScanSource(command, ci),
15250
+ repoUrl,
15251
+ sha,
15252
+ reference,
15253
+ shouldScan
15254
+ }
15255
+ );
15256
+ const callbackStates = [
15257
+ "Digested" /* Digested */,
15258
+ "Finished" /* Finished */
15259
+ ];
15260
+ const callback = (_analysisId) => digestSpinner.update({
15261
+ text: progressMassages.processingVulnerabilityReportSuccess
15262
+ });
15263
+ if (polling) {
15264
+ debug21(
15265
+ "[_digestReport] Using POLLING mode for analysis state updates (--polling flag enabled)"
15266
+ );
15267
+ console.log(
15268
+ chalk7.cyan(
15269
+ "\u{1F504} [Polling Mode] Using HTTP polling instead of WebSocket for status updates"
15270
+ )
15271
+ );
15272
+ await gqlClient.pollForAnalysisState({
15273
+ analysisId: fixReportId,
15274
+ callback,
15275
+ callbackStates,
15276
+ timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
15183
15277
  });
15184
- return result.trim();
15185
- } else if (platform2 === "win32") {
15186
- return process.env["COMPUTERNAME"] || null;
15187
15278
  } else {
15188
- try {
15189
- const result2 = execSync("hostnamectl --static", {
15190
- encoding: "utf-8",
15191
- timeout: 1e3
15192
- });
15193
- const hostname = result2.trim();
15194
- if (hostname) return hostname;
15195
- } catch {
15196
- }
15197
- const result = execSync("cat /etc/hostname", {
15198
- encoding: "utf-8",
15199
- timeout: 1e3
15279
+ debug21(
15280
+ "[_digestReport] Using WEBSOCKET mode for analysis state updates (default)"
15281
+ );
15282
+ console.log(
15283
+ chalk7.cyan(
15284
+ "\u{1F50C} [WebSocket Mode] Using WebSocket subscription for status updates"
15285
+ )
15286
+ );
15287
+ await gqlClient.subscribeToAnalysis({
15288
+ subscribeToAnalysisParams: {
15289
+ analysisId: fixReportId
15290
+ },
15291
+ callback,
15292
+ callbackStates,
15293
+ timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
15200
15294
  });
15201
- return result.trim();
15202
15295
  }
15203
- } catch (error) {
15204
- return null;
15296
+ const vulnFiles = await gqlClient.getVulnerabilityReportPaths(
15297
+ vulnerabilityReportId
15298
+ );
15299
+ digestSpinner.success({
15300
+ text: progressMassages.processingVulnerabilityReportSuccess
15301
+ });
15302
+ return vulnFiles;
15303
+ } catch (e) {
15304
+ const errorMessage = e instanceof ReportDigestError ? e.getDisplayMessage() : ReportDigestError.defaultMessage;
15305
+ digestSpinner.error({
15306
+ text: errorMessage
15307
+ });
15308
+ throw e;
15309
+ }
15310
+ }
15311
+ async function waitForAnaysisAndReviewPr({
15312
+ repo,
15313
+ githubActionToken,
15314
+ analysisId,
15315
+ scanner,
15316
+ gqlClient,
15317
+ polling
15318
+ }) {
15319
+ const params = z31.object({
15320
+ repo: z31.string().url(),
15321
+ githubActionToken: z31.string()
15322
+ }).parse({ repo, githubActionToken });
15323
+ const scm = await createScmLib(
15324
+ {
15325
+ url: params.repo,
15326
+ accessToken: params.githubActionToken,
15327
+ scmOrg: "",
15328
+ scmType: "GITHUB" /* GITHUB */
15329
+ },
15330
+ {
15331
+ propagateExceptions: true
15332
+ }
15333
+ );
15334
+ const callback = (analysisId2) => {
15335
+ return addFixCommentsForPr({
15336
+ analysisId: analysisId2,
15337
+ gqlClient,
15338
+ scm,
15339
+ scanner: z31.nativeEnum(SCANNERS).parse(scanner)
15340
+ });
15341
+ };
15342
+ if (polling) {
15343
+ debug21(
15344
+ "[waitForAnaysisAndReviewPr] Using POLLING mode for analysis state updates"
15345
+ );
15346
+ console.log(
15347
+ chalk7.cyan(
15348
+ "\u{1F504} [Polling Mode] Waiting for analysis completion using HTTP polling"
15349
+ )
15350
+ );
15351
+ await gqlClient.pollForAnalysisState({
15352
+ analysisId,
15353
+ callback,
15354
+ callbackStates: ["Finished" /* Finished */]
15355
+ });
15356
+ } else {
15357
+ debug21(
15358
+ "[waitForAnaysisAndReviewPr] Using WEBSOCKET mode for analysis state updates"
15359
+ );
15360
+ console.log(
15361
+ chalk7.cyan(
15362
+ "\u{1F50C} [WebSocket Mode] Waiting for analysis completion using WebSocket"
15363
+ )
15364
+ );
15365
+ await gqlClient.subscribeToAnalysis({
15366
+ subscribeToAnalysisParams: {
15367
+ analysisId
15368
+ },
15369
+ callback,
15370
+ callbackStates: ["Finished" /* Finished */]
15371
+ });
15205
15372
  }
15206
15373
  }
15207
- function stripHostnameSuffixes(hostname) {
15208
- if (!hostname) return hostname;
15209
- let normalized = hostname;
15210
- for (const suffix of HOSTNAME_SUFFIXES) {
15211
- if (normalized.endsWith(suffix)) {
15212
- normalized = normalized.slice(0, -suffix.length);
15213
- break;
15214
- }
15374
+
15375
+ // src/commands/scan_skill_input.ts
15376
+ import fs11 from "fs";
15377
+ import path11 from "path";
15378
+ import AdmZip2 from "adm-zip";
15379
+ var LOCAL_SKILL_ZIP_DATA_URL_PREFIX = "data:application/zip;base64,";
15380
+ var MAX_LOCAL_SKILL_ARCHIVE_BYTES = 2 * 1024 * 1024;
15381
+ async function resolveSkillScanInput(skillInput) {
15382
+ const resolvedPath = path11.resolve(skillInput);
15383
+ if (!fs11.existsSync(resolvedPath)) {
15384
+ return skillInput;
15215
15385
  }
15216
- return normalized;
15386
+ const stat = fs11.statSync(resolvedPath);
15387
+ if (!stat.isDirectory()) {
15388
+ throw new CliError(
15389
+ "Local skill input must be a directory containing SKILL.md"
15390
+ );
15391
+ }
15392
+ return packageLocalSkillDirectory(resolvedPath);
15217
15393
  }
15218
- function generateStableComputerName() {
15219
- const osSpecificName = getOsSpecificComputerName();
15220
- if (osSpecificName) {
15221
- return osSpecificName;
15394
+ function packageLocalSkillDirectory(directoryPath) {
15395
+ const zip = new AdmZip2();
15396
+ const rootPath = path11.resolve(directoryPath);
15397
+ const filePaths = listDirectoryFiles(rootPath);
15398
+ let hasSkillMd = false;
15399
+ for (const relativePath of filePaths) {
15400
+ const fullPath = path11.join(rootPath, relativePath);
15401
+ const normalizedFullPath = path11.resolve(fullPath);
15402
+ if (normalizedFullPath !== rootPath && !normalizedFullPath.startsWith(rootPath + path11.sep)) {
15403
+ continue;
15404
+ }
15405
+ const fileContent = fs11.readFileSync(normalizedFullPath);
15406
+ zip.addFile(relativePath, fileContent);
15407
+ if (path11.basename(relativePath).toLowerCase() === "skill.md") {
15408
+ hasSkillMd = true;
15409
+ }
15222
15410
  }
15223
- const currentHostname = os2.hostname();
15224
- return stripHostnameSuffixes(currentHostname);
15411
+ if (!hasSkillMd) {
15412
+ throw new CliError("Local skill directory must contain a SKILL.md file");
15413
+ }
15414
+ const zipBuffer = zip.toBuffer();
15415
+ if (zipBuffer.length > MAX_LOCAL_SKILL_ARCHIVE_BYTES) {
15416
+ throw new CliError(
15417
+ `Local skill directory is too large to scan via CLI (${zipBuffer.length} bytes > ${MAX_LOCAL_SKILL_ARCHIVE_BYTES} bytes)`
15418
+ );
15419
+ }
15420
+ return `${LOCAL_SKILL_ZIP_DATA_URL_PREFIX}${zipBuffer.toString("base64")}`;
15225
15421
  }
15226
- function getStableComputerName() {
15227
- const cached = configStore.get(STABLE_COMPUTER_NAME_CONFIG_KEY);
15228
- if (cached) {
15229
- return cached;
15422
+ function listDirectoryFiles(rootPath) {
15423
+ const files = [];
15424
+ function walk(relativeDir) {
15425
+ const absoluteDir = path11.join(rootPath, relativeDir);
15426
+ const entries = fs11.readdirSync(absoluteDir, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
15427
+ for (const entry of entries) {
15428
+ const safeEntryName = path11.basename(entry.name).replace(/\0/g, "");
15429
+ if (!safeEntryName) {
15430
+ continue;
15431
+ }
15432
+ const relativePath = relativeDir ? path11.posix.join(relativeDir, safeEntryName) : safeEntryName;
15433
+ const absolutePath = path11.join(rootPath, relativePath);
15434
+ const normalizedAbsolutePath = path11.resolve(absolutePath);
15435
+ if (normalizedAbsolutePath !== rootPath && !normalizedAbsolutePath.startsWith(rootPath + path11.sep)) {
15436
+ continue;
15437
+ }
15438
+ if (entry.isSymbolicLink()) {
15439
+ continue;
15440
+ }
15441
+ if (entry.isDirectory()) {
15442
+ walk(relativePath);
15443
+ continue;
15444
+ }
15445
+ if (entry.isFile()) {
15446
+ files.push(relativePath);
15447
+ }
15448
+ }
15230
15449
  }
15231
- const currentName = generateStableComputerName();
15232
- configStore.set(STABLE_COMPUTER_NAME_CONFIG_KEY, currentName);
15233
- return currentName;
15450
+ walk("");
15451
+ return files;
15234
15452
  }
15235
15453
 
15236
- // src/utils/sanitize-sensitive-data.ts
15237
- import { OpenRedaction } from "@openredaction/openredaction";
15238
- var openRedaction = new OpenRedaction({
15239
- patterns: [
15240
- // Core Personal Data
15241
- // Removed EMAIL - causes false positives in code/test snippets (e.g. --author="Eve Author <eve@example.com>")
15242
- // Prefer false negatives over false positives for this use case.
15243
- "SSN",
15244
- "NATIONAL_INSURANCE_UK",
15245
- "DATE_OF_BIRTH",
15246
- // Identity Documents
15247
- "PASSPORT_UK",
15248
- "PASSPORT_US",
15249
- "PASSPORT_MRZ_TD1",
15250
- "PASSPORT_MRZ_TD3",
15251
- "DRIVING_LICENSE_UK",
15252
- "DRIVING_LICENSE_US",
15253
- "VISA_NUMBER",
15254
- "VISA_MRZ",
15255
- "TAX_ID",
15256
- // Financial Data (removed SWIFT_BIC, CARD_AUTH_CODE - too broad, causing false positives with authentication words)
15257
- // Removed CREDIT_CARD - causes false positives on zero-filled UUIDs (e.g. '00000000-0000-0000-0000-000000000000')
15258
- // Prefer false negatives over false positives for this use case.
15259
- "IBAN",
15260
- "BANK_ACCOUNT_UK",
15261
- "ROUTING_NUMBER_US",
15262
- "CARD_TRACK1_DATA",
15263
- "CARD_TRACK2_DATA",
15264
- "CARD_EXPIRY",
15265
- // Cryptocurrency (removed BITCOIN_ADDRESS - too broad, matches hash-like strings)
15266
- "ETHEREUM_ADDRESS",
15267
- "LITECOIN_ADDRESS",
15268
- "CARDANO_ADDRESS",
15269
- "SOLANA_ADDRESS",
15270
- "MONERO_ADDRESS",
15271
- "RIPPLE_ADDRESS",
15272
- // Medical Data (removed PRESCRIPTION_NUMBER - too broad, matches words containing "ription")
15273
- // Removed MEDICAL_RECORD_NUMBER - too broad, "MR" prefix matches "Merge Request" in SCM contexts (e.g. "MR branches" → "MR br****es")
15274
- "NHS_NUMBER",
15275
- "AUSTRALIAN_MEDICARE",
15276
- "HEALTH_PLAN_NUMBER",
15277
- "PATIENT_ID",
15278
- // Communications (removed EMERGENCY_CONTACT, ADDRESS_PO_BOX, ZIP_CODE_US, PHONE_US, PHONE_INTERNATIONAL - too broad, causing false positives)
15279
- "PHONE_UK",
15280
- "PHONE_UK_MOBILE",
15281
- "PHONE_LINE_NUMBER",
15282
- "ADDRESS_STREET",
15283
- "POSTCODE_UK",
15284
- // Network & Technical
15285
- "IPV4",
15286
- "IPV6",
15287
- "MAC_ADDRESS",
15288
- "URL_WITH_AUTH",
15289
- // Security Keys & Tokens
15290
- "PRIVATE_KEY",
15291
- "SSH_PRIVATE_KEY",
15292
- "AWS_SECRET_KEY",
15293
- "AWS_ACCESS_KEY",
15294
- "AZURE_STORAGE_KEY",
15295
- "GCP_SERVICE_ACCOUNT",
15296
- "JWT_TOKEN",
15297
- "OAUTH_TOKEN",
15298
- "OAUTH_CLIENT_SECRET",
15299
- "BEARER_TOKEN",
15300
- "PAYMENT_TOKEN",
15301
- "GENERIC_SECRET",
15302
- "GENERIC_API_KEY",
15303
- // Platform-Specific API Keys
15304
- "GITHUB_TOKEN",
15305
- "SLACK_TOKEN",
15306
- "STRIPE_API_KEY",
15307
- "GOOGLE_API_KEY",
15308
- "FIREBASE_API_KEY",
15309
- "HEROKU_API_KEY",
15310
- "MAILGUN_API_KEY",
15311
- "SENDGRID_API_KEY",
15312
- "TWILIO_API_KEY",
15313
- "NPM_TOKEN",
15314
- "PYPI_TOKEN",
15315
- "DOCKER_AUTH",
15316
- "KUBERNETES_SECRET",
15317
- // Government & Legal
15318
- // Removed CLIENT_ID - too broad, "client" is ubiquitous in code (npm packages like @scope/client-*, class names like ClientSdkOptions)
15319
- "POLICE_REPORT_NUMBER",
15320
- "IMMIGRATION_NUMBER",
15321
- "COURT_REPORTER_LICENSE"
15322
- ]
15323
- });
15324
- function maskString(str, showStart = 2, showEnd = 2) {
15325
- if (str.length <= showStart + showEnd) {
15326
- return "*".repeat(str.length);
15454
+ // src/commands/index.ts
15455
+ async function review(params, { skipPrompts = true } = {}) {
15456
+ const {
15457
+ repo,
15458
+ f: scanFile,
15459
+ ref,
15460
+ apiKey,
15461
+ commitHash,
15462
+ mobbProjectName,
15463
+ pullRequest,
15464
+ githubToken,
15465
+ scanner,
15466
+ srcPath,
15467
+ polling
15468
+ } = params;
15469
+ await runAnalysis(
15470
+ {
15471
+ repo,
15472
+ scanFile,
15473
+ ref,
15474
+ apiKey,
15475
+ ci: true,
15476
+ commitHash,
15477
+ experimentalEnabled: false,
15478
+ mobbProjectName,
15479
+ pullRequest,
15480
+ githubToken,
15481
+ scanner,
15482
+ command: "review",
15483
+ srcPath,
15484
+ polling
15485
+ },
15486
+ { skipPrompts }
15487
+ );
15488
+ }
15489
+ async function analyze({
15490
+ repo,
15491
+ f: scanFile,
15492
+ ref,
15493
+ apiKey,
15494
+ ci,
15495
+ commitHash,
15496
+ srcPath,
15497
+ mobbProjectName,
15498
+ organizationId,
15499
+ autoPr,
15500
+ createOnePr,
15501
+ commitDirectly,
15502
+ pullRequest,
15503
+ polling
15504
+ }, { skipPrompts = false } = {}) {
15505
+ !ci && await showWelcomeMessage(skipPrompts);
15506
+ await runAnalysis(
15507
+ {
15508
+ repo,
15509
+ scanFile,
15510
+ ref,
15511
+ apiKey,
15512
+ ci,
15513
+ commitHash,
15514
+ mobbProjectName,
15515
+ srcPath,
15516
+ organizationId,
15517
+ command: "analyze",
15518
+ autoPr,
15519
+ commitDirectly,
15520
+ pullRequest,
15521
+ createOnePr,
15522
+ polling
15523
+ },
15524
+ { skipPrompts }
15525
+ );
15526
+ }
15527
+ async function addScmToken(addScmTokenOptions) {
15528
+ const { apiKey, token, organization, scmType, url, refreshToken, ci } = addScmTokenOptions;
15529
+ const gqlClient = await getAuthenticatedGQLClient({
15530
+ inputApiKey: apiKey,
15531
+ isSkipPrompts: ci
15532
+ });
15533
+ if (!scmType) {
15534
+ throw new CliError(errorMessages.invalidScmType);
15535
+ }
15536
+ const resp = await gqlClient.updateScmToken({
15537
+ scmType,
15538
+ url,
15539
+ token,
15540
+ org: organization,
15541
+ refreshToken
15542
+ });
15543
+ if (resp.updateScmToken?.__typename === "RepoUnreachableError") {
15544
+ throw new CliError(
15545
+ "Mobb could not reach the repository. Please try again. If Mobb is connected through a broker, please make sure the broker is connected."
15546
+ );
15547
+ } else if (resp.updateScmToken?.__typename === "BadScmCredentials") {
15548
+ throw new CliError("Invalid SCM credentials. Please try again.");
15549
+ } else if (resp.updateScmToken?.__typename === "ScmAccessTokenUpdateSuccess") {
15550
+ console.log("Token added successfully");
15551
+ } else {
15552
+ throw new CliError("Unexpected error, failed to add token");
15327
15553
  }
15328
- return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
15329
15554
  }
15330
- async function sanitizeDataWithCounts(obj) {
15331
- const counts = {
15332
- detections: { total: 0, high: 0, medium: 0, low: 0 }
15333
- };
15334
- const sanitizeString = async (str) => {
15335
- let result = str;
15336
- const piiDetections = openRedaction.scan(str);
15337
- if (piiDetections && piiDetections.total > 0) {
15338
- const allDetections = [
15339
- ...piiDetections.high,
15340
- ...piiDetections.medium,
15341
- ...piiDetections.low
15342
- ];
15343
- for (const detection of allDetections) {
15344
- counts.detections.total++;
15345
- if (detection.severity === "high") counts.detections.high++;
15346
- else if (detection.severity === "medium") counts.detections.medium++;
15347
- else if (detection.severity === "low") counts.detections.low++;
15348
- const masked = maskString(detection.value);
15349
- result = result.replaceAll(detection.value, masked);
15555
+ async function scan(scanOptions, { skipPrompts = false } = {}) {
15556
+ const { scanner, ci } = scanOptions;
15557
+ !ci && await showWelcomeMessage(skipPrompts);
15558
+ const selectedScanner = scanner || await choseScanner();
15559
+ if (selectedScanner !== SCANNERS.Checkmarx && selectedScanner !== SCANNERS.Snyk) {
15560
+ throw new CliError(
15561
+ "Vulnerability scanning via Bugsy is available only with Snyk and Checkmarx at the moment. Additional scanners will follow soon."
15562
+ );
15563
+ }
15564
+ selectedScanner === SCANNERS.Checkmarx && validateCheckmarxInstallation();
15565
+ if (selectedScanner === SCANNERS.Checkmarx && !scanOptions.cxProjectName) {
15566
+ throw new CliError(errorMessages.missingCxProjectName);
15567
+ }
15568
+ await runAnalysis(
15569
+ { ...scanOptions, scanner: selectedScanner, command: "scan" },
15570
+ { skipPrompts }
15571
+ );
15572
+ }
15573
+ async function showWelcomeMessage(skipPrompts = false) {
15574
+ console.log(mobbAscii);
15575
+ const welcome = chalkAnimation.rainbow("\n Welcome to Bugsy\n");
15576
+ skipPrompts ? await sleep(100) : await sleep(2e3);
15577
+ welcome.stop();
15578
+ }
15579
+ var VERDICT_COLORS = {
15580
+ BENIGN: "green",
15581
+ WARNING: "yellow",
15582
+ SUSPICIOUS: "magenta",
15583
+ MALICIOUS: "red"
15584
+ };
15585
+ var SEVERITY_COLORS = {
15586
+ CRITICAL: "red",
15587
+ HIGH: "magenta",
15588
+ MEDIUM: "yellow",
15589
+ LOW: "cyan"
15590
+ };
15591
+ async function scanSkill(options) {
15592
+ const { url, apiKey, ci } = options;
15593
+ const gqlClient = await getAuthenticatedGQLClient({
15594
+ inputApiKey: apiKey,
15595
+ isSkipPrompts: ci
15596
+ });
15597
+ console.log(chalk8.dim(`Scanning skill: ${url}`));
15598
+ console.log();
15599
+ const skillUrl = await resolveSkillScanInput(url);
15600
+ const result = await gqlClient.scanSkill({ skillUrl });
15601
+ const scan2 = result.scanSkill;
15602
+ const verdictColor = VERDICT_COLORS[scan2.verdict] ?? "white";
15603
+ console.log(
15604
+ chalk8.bold(`Verdict: `) + chalk8[verdictColor](
15605
+ scan2.verdict
15606
+ )
15607
+ );
15608
+ console.log(`Skill: ${scan2.skillName}`);
15609
+ if (scan2.skillVersion) {
15610
+ console.log(`Version: ${scan2.skillVersion}`);
15611
+ }
15612
+ console.log(`Hash: ${scan2.skillHash ?? "N/A"}`);
15613
+ console.log(`Findings: ${scan2.findingsCount}`);
15614
+ console.log(`Duration: ${scan2.scanDurationMs}ms`);
15615
+ if (scan2.cached) {
15616
+ console.log(chalk8.dim("(cached result)"));
15617
+ }
15618
+ console.log();
15619
+ if (scan2.findings.length > 0) {
15620
+ console.log(chalk8.bold("Findings:"));
15621
+ console.log();
15622
+ for (const f of scan2.findings) {
15623
+ const sevColor = SEVERITY_COLORS[f.severity] ?? "white";
15624
+ const location = [f.filePath, f.lineNumber].filter(Boolean).join(":");
15625
+ console.log(
15626
+ ` ${chalk8[sevColor](f.severity)} [${f.layer}] ${f.category}${f.ruleId ? ` (${f.ruleId})` : ""}`
15627
+ );
15628
+ if (location) {
15629
+ console.log(` ${chalk8.dim(location)}`);
15350
15630
  }
15351
- }
15352
- return result;
15353
- };
15354
- const sanitizeRecursive = async (data) => {
15355
- if (typeof data === "string") {
15356
- return sanitizeString(data);
15357
- } else if (Array.isArray(data)) {
15358
- return Promise.all(data.map((item) => sanitizeRecursive(item)));
15359
- } else if (data instanceof Error) {
15360
- return data;
15361
- } else if (data instanceof Date) {
15362
- return data;
15363
- } else if (typeof data === "object" && data !== null) {
15364
- const sanitized = {};
15365
- const record = data;
15366
- for (const key in record) {
15367
- if (Object.prototype.hasOwnProperty.call(record, key)) {
15368
- sanitized[key] = await sanitizeRecursive(record[key]);
15369
- }
15631
+ console.log(` ${f.explanation}`);
15632
+ if (f.evidence) {
15633
+ console.log(
15634
+ ` ${String(chalk8.dim("Evidence: " + f.evidence.slice(0, 120))).replace(/\n|\r/g, "")}`
15635
+ );
15370
15636
  }
15371
- return sanitized;
15637
+ console.log();
15372
15638
  }
15373
- return data;
15374
- };
15375
- const sanitizedData = await sanitizeRecursive(obj);
15376
- return { sanitizedData, counts };
15639
+ }
15640
+ if (scan2.summary) {
15641
+ console.log(chalk8.bold("Analysis:"));
15642
+ console.log(` ${scan2.summary}`);
15643
+ console.log();
15644
+ }
15645
+ if (scan2.verdict === "MALICIOUS" || scan2.verdict === "SUSPICIOUS") {
15646
+ process.exit(2);
15647
+ }
15377
15648
  }
15378
15649
 
15379
- // src/args/commands/upload_ai_blame.ts
15380
- var defaultLogger2 = {
15381
- info: (msg, data) => {
15382
- if (data !== void 0) {
15383
- console.log(msg, data);
15384
- } else {
15385
- console.log(msg);
15386
- }
15387
- },
15388
- error: (msg, data) => {
15389
- if (data !== void 0) {
15390
- console.error(msg, data);
15391
- } else {
15392
- console.error(msg);
15393
- }
15394
- }
15395
- };
15396
- var PromptItemZ = z32.object({
15397
- type: z32.enum([
15398
- "USER_PROMPT",
15399
- "AI_RESPONSE",
15400
- "TOOL_EXECUTION",
15401
- "AI_THINKING",
15402
- "MCP_TOOL_CALL"
15403
- // MCP (Model Context Protocol) tool invocation
15404
- ]),
15405
- attachedFiles: z32.array(
15406
- z32.object({
15407
- relativePath: z32.string(),
15408
- startLine: z32.number().optional()
15409
- })
15410
- ).optional(),
15411
- tokens: z32.object({
15412
- inputCount: z32.number(),
15413
- outputCount: z32.number()
15414
- }).optional(),
15415
- text: z32.string().optional(),
15416
- date: z32.date().optional(),
15417
- tool: z32.object({
15418
- name: z32.string(),
15419
- parameters: z32.string(),
15420
- result: z32.string(),
15421
- rawArguments: z32.string().optional(),
15422
- accepted: z32.boolean().optional(),
15423
- // MCP-specific fields (only populated for MCP_TOOL_CALL type)
15424
- mcpServer: z32.string().optional(),
15425
- // MCP server name (e.g., "datadog", "mobb-mcp")
15426
- mcpToolName: z32.string().optional()
15427
- // MCP tool name without prefix (e.g., "scan_and_fix_vulnerabilities")
15428
- }).optional()
15650
+ // src/args/validation.ts
15651
+ import chalk9 from "chalk";
15652
+ import path12 from "path";
15653
+ import { z as z32 } from "zod";
15654
+ function throwRepoUrlErrorMessage({
15655
+ error,
15656
+ repoUrl,
15657
+ command
15658
+ }) {
15659
+ const errorMessage = error.issues[error.issues.length - 1]?.message;
15660
+ const formattedErrorMessage = `
15661
+ Error: ${chalk9.bold(
15662
+ repoUrl
15663
+ )} is ${errorMessage}
15664
+ Example:
15665
+ mobbdev ${command} -r ${chalk9.bold(
15666
+ "https://github.com/WebGoat/WebGoat"
15667
+ )}`;
15668
+ throw new CliError(formattedErrorMessage);
15669
+ }
15670
+ var UrlZ = z32.string({
15671
+ invalid_type_error: `is not a valid ${Object.values(ScmType).join("/ ")} URL`
15429
15672
  });
15430
- var PromptItemArrayZ = z32.array(PromptItemZ);
15431
- async function getRepositoryUrl() {
15432
- try {
15433
- const gitService = new GitService(process.cwd());
15434
- const isRepo = await gitService.isGitRepository();
15435
- if (!isRepo) {
15436
- return null;
15437
- }
15438
- const remoteUrl = await gitService.getRemoteUrl();
15439
- const parsed = parseScmURL(remoteUrl);
15440
- return parsed?.scmType === "GitHub" /* GitHub */ || parsed?.scmType === "GitLab" /* GitLab */ ? remoteUrl : null;
15441
- } catch {
15442
- return null;
15673
+ function validateOrganizationId(organizationId) {
15674
+ const orgIdValidation = z32.string().uuid().nullish().safeParse(organizationId);
15675
+ if (!orgIdValidation.success) {
15676
+ throw new CliError(`organizationId: ${organizationId} is not a valid UUID`);
15443
15677
  }
15444
15678
  }
15445
- function getSystemInfo() {
15446
- let userName;
15447
- try {
15448
- userName = os3.userInfo().username;
15449
- } catch {
15450
- userName = void 0;
15679
+ function validateRepoUrl(args) {
15680
+ const repoSafeParseResult = UrlZ.safeParse(args.repo);
15681
+ const { success } = repoSafeParseResult;
15682
+ const [command] = args._;
15683
+ if (!command) {
15684
+ throw new CliError("Command not found");
15685
+ }
15686
+ if (!success) {
15687
+ throwRepoUrlErrorMessage({
15688
+ error: repoSafeParseResult.error,
15689
+ repoUrl: args.repo,
15690
+ command
15691
+ });
15451
15692
  }
15452
- return {
15453
- computerName: getStableComputerName(),
15454
- userName
15455
- };
15456
15693
  }
15457
- function uploadAiBlameBuilder(args) {
15458
- return args.option("prompt", {
15459
- type: "string",
15460
- array: true,
15461
- demandOption: true,
15462
- describe: chalk10.bold("Path(s) to prompt artifact(s) (one per session)")
15463
- }).option("inference", {
15464
- type: "string",
15465
- array: true,
15466
- demandOption: true,
15467
- describe: chalk10.bold(
15468
- "Path(s) to inference artifact(s) (one per session)"
15469
- )
15470
- }).option("ai-response-at", {
15694
+ var supportExtensions = [".json", ".xml", ".fpr", ".sarif", ".zip"];
15695
+ function validateReportFileFormat(reportFile) {
15696
+ if (!supportExtensions.includes(path12.extname(reportFile))) {
15697
+ throw new CliError(
15698
+ `
15699
+ ${chalk9.bold(
15700
+ reportFile
15701
+ )} is not a supported file extension. Supported extensions are: ${chalk9.bold(
15702
+ supportExtensions.join(", ")
15703
+ )}
15704
+ `
15705
+ );
15706
+ }
15707
+ }
15708
+
15709
+ // src/args/commands/analyze.ts
15710
+ function analyzeBuilder(yargs2) {
15711
+ return yargs2.option("f", {
15712
+ alias: "scan-file",
15471
15713
  type: "string",
15472
- array: true,
15473
15714
  describe: chalk10.bold(
15474
- "ISO timestamp(s) for AI response (one per session, defaults to now)"
15715
+ "Select the vulnerability report to analyze (Checkmarx, Snyk, Fortify, CodeQL, Sonarqube, Semgrep, Datadog)"
15475
15716
  )
15476
- }).option("model", {
15477
- type: "string",
15478
- array: true,
15479
- describe: chalk10.bold("AI model name(s) (optional, one per session)")
15480
- }).option("tool-name", {
15481
- type: "string",
15482
- array: true,
15483
- describe: chalk10.bold("Tool/IDE name(s) (optional, one per session)")
15484
- }).option("blame-type", {
15485
- type: "string",
15486
- array: true,
15487
- choices: Object.values(AiBlameInferenceType),
15717
+ }).option("repo", repoOption).option("p", {
15718
+ alias: "src-path",
15488
15719
  describe: chalk10.bold(
15489
- "Blame type(s) (optional, one per session, defaults to CHAT)"
15490
- )
15491
- }).strict();
15492
- }
15493
- async function uploadAiBlameHandlerFromExtension(args) {
15494
- const uploadArgs = {
15495
- prompt: [],
15496
- inference: [],
15497
- model: [],
15498
- toolName: [],
15499
- aiResponseAt: [],
15500
- blameType: [],
15501
- sessionId: []
15502
- };
15503
- let promptsCounts;
15504
- let inferenceCounts;
15505
- let promptsUUID;
15506
- let inferenceUUID;
15507
- await withFile(async (promptFile) => {
15508
- const promptsResult = await sanitizeDataWithCounts(args.prompts);
15509
- promptsCounts = promptsResult.counts;
15510
- promptsUUID = path12.basename(promptFile.path, path12.extname(promptFile.path));
15511
- await fsPromises3.writeFile(
15512
- promptFile.path,
15513
- JSON.stringify(promptsResult.sanitizedData, null, 2),
15514
- "utf-8"
15515
- );
15516
- uploadArgs.prompt.push(promptFile.path);
15517
- await withFile(async (inferenceFile) => {
15518
- const inferenceResult = await sanitizeDataWithCounts(args.inference);
15519
- inferenceCounts = inferenceResult.counts;
15520
- inferenceUUID = path12.basename(
15521
- inferenceFile.path,
15522
- path12.extname(inferenceFile.path)
15523
- );
15524
- await fsPromises3.writeFile(
15525
- inferenceFile.path,
15526
- inferenceResult.sanitizedData,
15527
- "utf-8"
15528
- );
15529
- uploadArgs.inference.push(inferenceFile.path);
15530
- uploadArgs.model.push(args.model);
15531
- uploadArgs.toolName.push(args.tool);
15532
- uploadArgs.aiResponseAt.push(args.responseTime);
15533
- uploadArgs.blameType.push(args.blameType || "CHAT" /* Chat */);
15534
- if (args.sessionId) {
15535
- uploadArgs.sessionId.push(args.sessionId);
15536
- }
15537
- await uploadAiBlameHandler({
15538
- args: uploadArgs,
15539
- exitOnError: false,
15540
- apiUrl: args.apiUrl,
15541
- webAppUrl: args.webAppUrl,
15542
- repositoryUrl: args.repositoryUrl
15543
- });
15544
- });
15545
- });
15546
- return {
15547
- promptsCounts,
15548
- inferenceCounts,
15549
- promptsUUID,
15550
- inferenceUUID
15551
- };
15720
+ "Path to the repository folder with the source code; alternatively, you can specify the Fortify FPR file to extract source code out of it"
15721
+ ),
15722
+ type: "string"
15723
+ }).option("ref", refOption).option("ch", {
15724
+ alias: "commit-hash",
15725
+ describe: chalk10.bold("Hash of the commit"),
15726
+ type: "string"
15727
+ }).option("mobb-project-name", mobbProjectNameOption).option("y", yesOption).option("ci", ciOption).option("org", organizationIdOptions).option("api-key", apiKeyOption).option("commit-hash", commitHashOption).option("auto-pr", autoPrOption).option("create-one-pr", createOnePrOption).option("commit-directly", commitDirectlyOption).option("pull-request", {
15728
+ alias: ["pr", "pr-number", "pr-id"],
15729
+ describe: chalk10.bold("Number of the pull request"),
15730
+ type: "number",
15731
+ demandOption: false
15732
+ }).option("polling", pollingOption).example(
15733
+ "npx mobbdev@latest analyze -r https://github.com/WebGoat/WebGoat -f <your_vulnerability_report_path>",
15734
+ "analyze an existing repository"
15735
+ ).help();
15552
15736
  }
15553
- async function uploadAiBlameHandler(options) {
15554
- const {
15555
- args,
15556
- exitOnError = true,
15557
- apiUrl,
15558
- webAppUrl,
15559
- logger: logger2 = defaultLogger2
15560
- } = options;
15561
- const prompts = args.prompt || [];
15562
- const inferences = args.inference || [];
15563
- const models = args.model || [];
15564
- const tools = args.toolName || args["tool-name"] || [];
15565
- const responseTimes = args.aiResponseAt || args["ai-response-at"] || [];
15566
- const blameTypes = args.blameType || args["blame-type"] || [];
15567
- const sessionIds = args.sessionId || args["session-id"] || [];
15568
- if (prompts.length !== inferences.length) {
15569
- const errorMsg = "prompt and inference must have the same number of entries";
15570
- logger2.error(chalk10.red(errorMsg));
15571
- if (exitOnError) {
15572
- process.exit(1);
15573
- }
15574
- throw new Error(errorMsg);
15737
+ function validateAnalyzeOptions(argv) {
15738
+ if (argv.f && !fs12.existsSync(argv.f)) {
15739
+ throw new CliError(`
15740
+ Can't access ${chalk10.bold(argv.f)}`);
15575
15741
  }
15576
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
15577
- const { computerName, userName } = getSystemInfo();
15578
- const repositoryUrl = options.repositoryUrl !== void 0 ? options.repositoryUrl : await getRepositoryUrl();
15579
- const sessions = [];
15580
- for (let i = 0; i < prompts.length; i++) {
15581
- const promptPath = String(prompts[i]);
15582
- const inferencePath = String(inferences[i]);
15583
- try {
15584
- await Promise.all([
15585
- fsPromises3.access(promptPath),
15586
- fsPromises3.access(inferencePath)
15587
- ]);
15588
- } catch {
15589
- const errorMsg = `File not found for session ${i + 1}`;
15590
- logger2.error(chalk10.red(errorMsg));
15591
- if (exitOnError) {
15592
- process.exit(1);
15593
- }
15594
- throw new Error(errorMsg);
15595
- }
15596
- sessions.push({
15597
- promptFileName: path12.basename(promptPath),
15598
- inferenceFileName: path12.basename(inferencePath),
15599
- aiResponseAt: responseTimes[i] || nowIso,
15600
- model: models[i],
15601
- toolName: tools[i],
15602
- blameType: blameTypes[i] || "CHAT" /* Chat */,
15603
- computerName,
15604
- userName,
15605
- sessionId: sessionIds[i],
15606
- repositoryUrl
15607
- });
15742
+ validateOrganizationId(argv.organizationId);
15743
+ if (!argv.srcPath && !argv.repo) {
15744
+ throw new CliError("You must supply either --src-path or --repo");
15608
15745
  }
15609
- const authenticatedClient = await getAuthenticatedGQLClient({
15610
- isSkipPrompts: true,
15611
- apiUrl,
15612
- webAppUrl
15613
- });
15614
- const initSessions = sessions.map(
15615
- ({ sessionId: _sessionId, ...rest }) => rest
15616
- );
15617
- const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
15618
- sessions: initSessions
15619
- });
15620
- const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
15621
- if (uploadSessions.length !== sessions.length) {
15622
- const errorMsg = "Init failed to return expected number of sessions";
15623
- logger2.error(chalk10.red(errorMsg));
15624
- if (exitOnError) {
15625
- process.exit(1);
15626
- }
15627
- throw new Error(errorMsg);
15746
+ if (!argv.srcPath && argv.repo) {
15747
+ validateRepoUrl(argv);
15628
15748
  }
15629
- for (let i = 0; i < uploadSessions.length; i++) {
15630
- const us = uploadSessions[i];
15631
- const promptPath = String(prompts[i]);
15632
- const inferencePath = String(inferences[i]);
15633
- await Promise.all([
15634
- // Prompt
15635
- uploadFile({
15636
- file: promptPath,
15637
- url: us.prompt.url,
15638
- uploadFields: JSON.parse(us.prompt.uploadFieldsJSON),
15639
- uploadKey: us.prompt.uploadKey
15640
- }),
15641
- // Inference
15642
- uploadFile({
15643
- file: inferencePath,
15644
- url: us.inference.url,
15645
- uploadFields: JSON.parse(us.inference.uploadFieldsJSON),
15646
- uploadKey: us.inference.uploadKey
15647
- })
15648
- ]);
15749
+ if (argv.ci && !argv.apiKey) {
15750
+ throw new CliError("--ci flag requires --api-key to be provided as well");
15649
15751
  }
15650
- const finalizeSessions = uploadSessions.map((us, i) => {
15651
- const s = sessions[i];
15652
- return {
15653
- aiBlameInferenceId: us.aiBlameInferenceId,
15654
- promptKey: us.prompt.uploadKey,
15655
- inferenceKey: us.inference.uploadKey,
15656
- aiResponseAt: s.aiResponseAt,
15657
- model: s.model,
15658
- toolName: s.toolName,
15659
- blameType: s.blameType,
15660
- computerName: s.computerName,
15661
- userName: s.userName,
15662
- sessionId: s.sessionId,
15663
- repositoryUrl: s.repositoryUrl
15664
- };
15665
- });
15666
- try {
15667
- logger2.info(
15668
- `[UPLOAD] Calling finalizeAIBlameInferencesUploadRaw with ${finalizeSessions.length} sessions`
15752
+ if (argv.commitDirectly && !argv["auto-pr"]) {
15753
+ throw new CliError(
15754
+ "--commit-directly flag requires --auto-pr to be provided as well"
15669
15755
  );
15670
- const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw(
15671
- {
15672
- sessions: finalizeSessions
15673
- }
15756
+ }
15757
+ if (argv["create-one-pr"] && !argv["auto-pr"]) {
15758
+ throw new CliError(
15759
+ "--create-one-pr flag requires --auto-pr to be provided as well"
15674
15760
  );
15675
- logger2.info("[UPLOAD] Finalize response:", JSON.stringify(finRes, null, 2));
15676
- const status = finRes?.finalizeAIBlameInferencesUpload?.status;
15677
- if (status !== "OK") {
15678
- const errorMsg = finRes?.finalizeAIBlameInferencesUpload?.error || "unknown error";
15679
- logger2.error(
15680
- chalk10.red(
15681
- `[UPLOAD] Finalize failed with status: ${status}, error: ${errorMsg}`
15682
- )
15683
- );
15684
- if (exitOnError) {
15685
- process.exit(1);
15686
- }
15687
- throw new Error(errorMsg);
15688
- }
15689
- logger2.info(chalk10.green("[UPLOAD] AI Blame uploads finalized successfully"));
15690
- } catch (error) {
15691
- logger2.error("[UPLOAD] Finalize threw error:", error);
15692
- throw error;
15761
+ }
15762
+ if (argv["create-one-pr"] && argv.commitDirectly) {
15763
+ throw new CliError(
15764
+ "--create-one-pr and --commit-directly cannot be provided at the same time"
15765
+ );
15766
+ }
15767
+ if (argv.pullRequest && !argv.commitDirectly) {
15768
+ throw new CliError(
15769
+ "--pull-request flag requires --commit-directly to be provided as well"
15770
+ );
15771
+ }
15772
+ if (argv.f) {
15773
+ validateReportFileFormat(argv.f);
15693
15774
  }
15694
15775
  }
15695
- async function uploadAiBlameCommandHandler(args) {
15696
- await uploadAiBlameHandler({ args });
15776
+ async function analyzeHandler(args) {
15777
+ validateAnalyzeOptions(args);
15778
+ await analyze(args, { skipPrompts: args.yes });
15697
15779
  }
15698
15780
 
15699
15781
  // src/features/claude_code/data_collector.ts
15782
+ import { z as z33 } from "zod";
15700
15783
  init_client_generates();
15701
15784
  init_GitService();
15702
15785
  init_urlParser2();
@@ -24636,7 +24719,7 @@ async function processChat(client, cascadeId) {
24636
24719
  let repositoryUrl;
24637
24720
  if (repoOrigin) {
24638
24721
  const parsed = parseScmURL(repoOrigin);
24639
- if (parsed?.scmType === "GitHub" /* GitHub */) {
24722
+ if (parsed?.scmType === "GitHub" /* GitHub */ || parsed?.scmType === "GitLab" /* GitLab */) {
24640
24723
  repositoryUrl = parsed.canonicalUrl;
24641
24724
  }
24642
24725
  }
@@ -24937,13 +25020,13 @@ var parseArgs = async (args) => {
24937
25020
  };
24938
25021
 
24939
25022
  // src/index.ts
24940
- var debug20 = Debug20("mobbdev:index");
25023
+ var debug22 = Debug22("mobbdev:index");
24941
25024
  async function run() {
24942
25025
  return parseArgs(hideBin(process.argv));
24943
25026
  }
24944
25027
  (async () => {
24945
25028
  try {
24946
- debug20("Bugsy CLI v%s running...", packageJson.version);
25029
+ debug22("Bugsy CLI v%s running...", packageJson.version);
24947
25030
  await run();
24948
25031
  process.exit(0);
24949
25032
  } catch (err) {