@synkro-sh/cli 1.3.21 → 1.3.23

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/bootstrap.js CHANGED
@@ -2871,6 +2871,7 @@ var init_repoConnect = __esm({
2871
2871
  // cli/commands/setupGithub.ts
2872
2872
  var setupGithub_exports = {};
2873
2873
  __export(setupGithub_exports, {
2874
+ connectGitHub: () => connectGitHub,
2874
2875
  setupGithubCommand: () => setupGithubCommand
2875
2876
  });
2876
2877
  import { createInterface as createInterface2 } from "readline/promises";
@@ -2983,6 +2984,53 @@ async function apiCall(gatewayUrl, jwt2, path, opts = {}) {
2983
2984
  }
2984
2985
  return resp.json();
2985
2986
  }
2987
+ async function connectGitHub(gatewayUrl, jwt2, opts = {}) {
2988
+ try {
2989
+ const result = await apiCall(
2990
+ gatewayUrl,
2991
+ jwt2,
2992
+ "/api/v1/cli/github-token"
2993
+ );
2994
+ if (result.connected && result.token) {
2995
+ if (!opts.silent) console.log(" \u2713 GitHub already connected via Synkro.");
2996
+ return result.token;
2997
+ }
2998
+ } catch {
2999
+ }
3000
+ if (!opts.silent) console.log(" Opening browser to authorize GitHub...");
3001
+ try {
3002
+ const authResp = await apiCall(
3003
+ gatewayUrl,
3004
+ jwt2,
3005
+ "/api/pipes-widget/authorize/github",
3006
+ { method: "POST", body: "{}" }
3007
+ );
3008
+ openBrowser3(authResp.url);
3009
+ if (!opts.silent) console.log(" Waiting for authorization...");
3010
+ } catch (err) {
3011
+ if (!opts.silent) console.error(` Failed to start GitHub authorization: ${err.message}`);
3012
+ return null;
3013
+ }
3014
+ const deadline = Date.now() + 12e4;
3015
+ while (Date.now() < deadline) {
3016
+ await sleep(2e3);
3017
+ try {
3018
+ const result = await apiCall(
3019
+ gatewayUrl,
3020
+ jwt2,
3021
+ "/api/v1/cli/github-token"
3022
+ );
3023
+ if (result.connected && result.token) {
3024
+ if (!opts.silent) console.log("\n \u2713 GitHub connected!");
3025
+ return result.token;
3026
+ }
3027
+ } catch {
3028
+ }
3029
+ if (!opts.silent) process.stdout.write(".");
3030
+ }
3031
+ if (!opts.silent) console.error("\n Timed out waiting for GitHub authorization.");
3032
+ return null;
3033
+ }
2986
3034
  async function setupGithubCommand(opts = {}) {
2987
3035
  if (!isAuthenticated()) {
2988
3036
  console.error("Not authenticated. Run `synkro-cli login` first.");
@@ -3035,59 +3083,13 @@ async function setupGithubCommand(opts = {}) {
3035
3083
  }
3036
3084
  } else {
3037
3085
  console.log("\nConnecting to GitHub...");
3038
- let connected = false;
3039
- try {
3040
- const result = await apiCall(
3041
- gatewayUrl,
3042
- jwt2,
3043
- "/api/v1/cli/github-token"
3044
- );
3045
- if (result.connected && result.token) {
3046
- ghToken = result.token;
3047
- connected = true;
3048
- console.log(" \u2713 GitHub already connected via Synkro.\n");
3049
- }
3050
- } catch {
3051
- }
3052
- if (!connected) {
3053
- console.log(" Opening browser to authorize GitHub...");
3054
- try {
3055
- const authResp = await apiCall(
3056
- gatewayUrl,
3057
- jwt2,
3058
- "/api/pipes-widget/authorize/github",
3059
- { method: "POST", body: "{}" }
3060
- );
3061
- openBrowser3(authResp.url);
3062
- console.log(" Waiting for authorization...");
3063
- } catch (err) {
3064
- console.error(`Failed to start GitHub authorization: ${err.message}`);
3065
- process.exit(1);
3066
- }
3067
- const deadline = Date.now() + 12e4;
3068
- ghToken = "";
3069
- while (Date.now() < deadline) {
3070
- await sleep(2e3);
3071
- try {
3072
- const result = await apiCall(
3073
- gatewayUrl,
3074
- jwt2,
3075
- "/api/v1/cli/github-token"
3076
- );
3077
- if (result.connected && result.token) {
3078
- ghToken = result.token;
3079
- break;
3080
- }
3081
- } catch {
3082
- }
3083
- process.stdout.write(".");
3084
- }
3085
- if (!ghToken) {
3086
- console.error("\n Timed out waiting for GitHub authorization. Try again.");
3087
- process.exit(1);
3088
- }
3089
- console.log("\n \u2713 GitHub connected!\n");
3086
+ const token = await connectGitHub(gatewayUrl, jwt2);
3087
+ if (!token) {
3088
+ console.error("GitHub connection failed. Try again.");
3089
+ process.exit(1);
3090
3090
  }
3091
+ ghToken = token;
3092
+ console.log();
3091
3093
  }
3092
3094
  let claudeToken;
3093
3095
  if (!opts.skipClaudeToken) {
@@ -3242,7 +3244,6 @@ function parseArgs(argv) {
3242
3244
  else if (a === "--no-mcp") opts.noMcp = true;
3243
3245
  else if (a === "--force" || a === "-f") opts.force = true;
3244
3246
  else if (a === "--link-repo") opts.linkRepo = true;
3245
- else if (a === "--pr-scan") opts.prScan = true;
3246
3247
  }
3247
3248
  if (!opts.gatewayUrl) {
3248
3249
  const fromEnv = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL);
@@ -3332,7 +3333,7 @@ function writeConfigEnv(opts) {
3332
3333
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
3333
3334
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
3334
3335
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
3335
- `SYNKRO_VERSION=${shellQuoteSingle("1.3.21")}`
3336
+ `SYNKRO_VERSION=${shellQuoteSingle("1.3.23")}`
3336
3337
  ];
3337
3338
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
3338
3339
  if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
@@ -3530,6 +3531,13 @@ async function installCommand(opts = {}) {
3530
3531
  console.error("No access token available after auth.");
3531
3532
  process.exit(1);
3532
3533
  }
3534
+ console.log("\nConnecting to GitHub...");
3535
+ const ghToken = await connectGitHub(gatewayUrl, token);
3536
+ if (!ghToken) {
3537
+ console.error("GitHub connection is required. Re-run `synkro-cli install` to try again.");
3538
+ process.exit(1);
3539
+ }
3540
+ console.log();
3533
3541
  setApiBaseUrl(`${gatewayUrl}/api`);
3534
3542
  await promptRepoConnection({ linkRepo: opts.linkRepo });
3535
3543
  const agents = detectAgents();
@@ -3667,18 +3675,9 @@ async function installCommand(opts = {}) {
3667
3675
  `);
3668
3676
  }
3669
3677
  }
3670
- if (opts.prScan) {
3671
- console.log();
3672
- const { setupGithubCommand: setupGithubCommand2 } = await Promise.resolve().then(() => (init_setupGithub(), setupGithub_exports));
3673
- await setupGithubCommand2({ nonInteractive: true });
3674
- }
3678
+ const { setupGithubCommand: setupGithubCommand2 } = await Promise.resolve().then(() => (init_setupGithub(), setupGithub_exports));
3679
+ await setupGithubCommand2({ nonInteractive: true, githubToken: ghToken });
3675
3680
  console.log("\u2713 Synkro installed.");
3676
- console.log();
3677
- if (!opts.prScan) {
3678
- console.log("Next steps:");
3679
- console.log(" \u2022 synkro-cli setup-github (enable PR scanning)");
3680
- console.log(" \u2022 synkro-cli status (check what is configured)");
3681
- }
3682
3681
  }
3683
3682
  function detectGitRepo2() {
3684
3683
  try {
@@ -3883,6 +3882,7 @@ var init_install = __esm({
3883
3882
  init_stub();
3884
3883
  init_repoConnect();
3885
3884
  init_projects();
3885
+ init_setupGithub();
3886
3886
  SYNKRO_DIR2 = join6(homedir5(), ".synkro");
3887
3887
  HOOKS_DIR = join6(SYNKRO_DIR2, "hooks");
3888
3888
  BIN_DIR = join6(SYNKRO_DIR2, "bin");
@@ -4381,6 +4381,22 @@ function getPrFiles(repo, prNumber) {
4381
4381
  ]);
4382
4382
  return data;
4383
4383
  }
4384
+ async function fetchScanContext(gatewayUrl, apiKey, repo, prNumber, sha) {
4385
+ try {
4386
+ const url = `${gatewayUrl.replace(/\/$/, "")}/api/pr-scans/scan-context?repo=${encodeURIComponent(repo)}&pr_number=${prNumber}&sha=${sha}`;
4387
+ const headers = { "x-synkro-api-key": apiKey };
4388
+ const ghToken = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "";
4389
+ if (ghToken) headers["x-github-token"] = ghToken;
4390
+ const resp = await fetch(url, {
4391
+ headers,
4392
+ signal: AbortSignal.timeout(15e3)
4393
+ });
4394
+ if (!resp.ok) return { scan_all: true };
4395
+ return await resp.json();
4396
+ } catch {
4397
+ return { scan_all: true };
4398
+ }
4399
+ }
4384
4400
  function getFileDiffWithLines(file) {
4385
4401
  if (!file.patch) return { hunks: "", newFileLineMap: /* @__PURE__ */ new Map() };
4386
4402
  const lines = file.patch.split("\n");
@@ -4741,11 +4757,34 @@ async function scanPrCommand() {
4741
4757
  console.error("Failed to fetch PR files:", err.message);
4742
4758
  process.exit(2);
4743
4759
  }
4760
+ const scanCtx = await fetchScanContext(gatewayUrl, synkroApiKey, repo, prNumber, sha);
4761
+ if (scanCtx.skip) {
4762
+ console.log(`Already scanned at ${sha.slice(0, 7)}, skipping.
4763
+ `);
4764
+ postCheckRun(repo, sha, "success", []);
4765
+ await postEventToBackend({
4766
+ gatewayUrl,
4767
+ apiKey: synkroApiKey,
4768
+ repo,
4769
+ prNumber,
4770
+ sha,
4771
+ findings: [],
4772
+ filesScanned: 0,
4773
+ totalLatencyMs: 0
4774
+ });
4775
+ return;
4776
+ }
4777
+ const changedFiles = !scanCtx.scan_all && scanCtx.files ? new Set(scanCtx.files) : null;
4778
+ if (changedFiles) {
4779
+ console.log(`Incremental scan: ${changedFiles.size} file(s) changed since last scan (${scanCtx.last_sha?.slice(0, 7)}).
4780
+ `);
4781
+ }
4744
4782
  const eligible = files.filter((f) => {
4745
4783
  if (f.status === "removed") return false;
4746
4784
  if (shouldSkipFile(f.filename)) return false;
4747
4785
  if (f.additions + f.deletions > MAX_DIFF_LINES_PER_FILE) return false;
4748
4786
  if (!f.patch) return false;
4787
+ if (changedFiles && !changedFiles.has(f.filename)) return false;
4749
4788
  return true;
4750
4789
  });
4751
4790
  console.log(`${files.length} files in PR, ${eligible.length} eligible for scan.