@synkro-sh/cli 1.3.20 → 1.3.21
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 +187 -52
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -2495,6 +2495,14 @@ var init_workflowTemplate = __esm({
|
|
|
2495
2495
|
on:
|
|
2496
2496
|
pull_request:
|
|
2497
2497
|
types: [opened, synchronize, reopened]
|
|
2498
|
+
workflow_dispatch:
|
|
2499
|
+
inputs:
|
|
2500
|
+
pr_number:
|
|
2501
|
+
description: PR number to scan
|
|
2502
|
+
required: true
|
|
2503
|
+
sha:
|
|
2504
|
+
description: Commit SHA to scan
|
|
2505
|
+
required: true
|
|
2498
2506
|
|
|
2499
2507
|
jobs:
|
|
2500
2508
|
scan:
|
|
@@ -2507,6 +2515,7 @@ jobs:
|
|
|
2507
2515
|
- uses: actions/checkout@v4
|
|
2508
2516
|
with:
|
|
2509
2517
|
fetch-depth: 0
|
|
2518
|
+
ref: \${{ inputs.sha || github.event.pull_request.head.sha }}
|
|
2510
2519
|
|
|
2511
2520
|
- name: Cache npm globals
|
|
2512
2521
|
id: cache-npm-global
|
|
@@ -2527,9 +2536,9 @@ jobs:
|
|
|
2527
2536
|
CLAUDE_CODE_OAUTH_TOKEN: \${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
2528
2537
|
SYNKRO_API_KEY: \${{ secrets.SYNKRO_API_KEY }}
|
|
2529
2538
|
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
2530
|
-
SYNKRO_PR_NUMBER: \${{ github.event.pull_request.number }}
|
|
2539
|
+
SYNKRO_PR_NUMBER: \${{ inputs.pr_number || github.event.pull_request.number }}
|
|
2531
2540
|
SYNKRO_REPO: \${{ github.repository }}
|
|
2532
|
-
SYNKRO_SHA: \${{ github.event.pull_request.head.sha }}
|
|
2541
|
+
SYNKRO_SHA: \${{ inputs.sha || github.event.pull_request.head.sha }}
|
|
2533
2542
|
SYNKRO_GATEWAY_URL: \${{ vars.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh' }}
|
|
2534
2543
|
`;
|
|
2535
2544
|
WORKFLOW_PATH = ".github/workflows/synkro.yml";
|
|
@@ -2721,14 +2730,14 @@ function waitForGithubToken() {
|
|
|
2721
2730
|
});
|
|
2722
2731
|
}
|
|
2723
2732
|
function openBrowser2(url) {
|
|
2724
|
-
const { execFile:
|
|
2733
|
+
const { execFile: execFile3 } = __require("child_process");
|
|
2725
2734
|
const plat = process.platform;
|
|
2726
2735
|
const cb = (err) => {
|
|
2727
2736
|
if (err) console.log(` Open this URL manually: ${url}`);
|
|
2728
2737
|
};
|
|
2729
|
-
if (plat === "darwin")
|
|
2730
|
-
else if (plat === "win32")
|
|
2731
|
-
else
|
|
2738
|
+
if (plat === "darwin") execFile3("open", [url], cb);
|
|
2739
|
+
else if (plat === "win32") execFile3("cmd", ["/c", "start", "", url], cb);
|
|
2740
|
+
else execFile3("xdg-open", [url], cb);
|
|
2732
2741
|
}
|
|
2733
2742
|
async function connectGithubAndSelectRepos() {
|
|
2734
2743
|
const url = `${SYNKRO_WEB_AUTH_URL2}/cli-github?port=${GITHUB_PORT}`;
|
|
@@ -2868,8 +2877,9 @@ import { createInterface as createInterface2 } from "readline/promises";
|
|
|
2868
2877
|
import { stdin as input, stdout as output } from "process";
|
|
2869
2878
|
import { execSync as execSync3, spawn as nodeSpawn } from "child_process";
|
|
2870
2879
|
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
2871
|
-
import { homedir as homedir4 } from "os";
|
|
2880
|
+
import { homedir as homedir4, platform as platform2 } from "os";
|
|
2872
2881
|
import { join as join5 } from "path";
|
|
2882
|
+
import { execFile as execFile2 } from "child_process";
|
|
2873
2883
|
function readConfig() {
|
|
2874
2884
|
if (!existsSync6(CONFIG_PATH)) return {};
|
|
2875
2885
|
const out = {};
|
|
@@ -2897,9 +2907,7 @@ async function prompt(rl, q, opts = {}) {
|
|
|
2897
2907
|
resolve2(chunk);
|
|
2898
2908
|
return;
|
|
2899
2909
|
}
|
|
2900
|
-
if (s === "")
|
|
2901
|
-
process.exit(130);
|
|
2902
|
-
}
|
|
2910
|
+
if (s === "") process.exit(130);
|
|
2903
2911
|
if (s === "\x7F" || s === "\b") {
|
|
2904
2912
|
chunk = chunk.slice(0, -1);
|
|
2905
2913
|
return;
|
|
@@ -2911,6 +2919,30 @@ async function prompt(rl, q, opts = {}) {
|
|
|
2911
2919
|
}
|
|
2912
2920
|
return await rl.question(q);
|
|
2913
2921
|
}
|
|
2922
|
+
function openBrowser3(url) {
|
|
2923
|
+
const os = platform2();
|
|
2924
|
+
let bin;
|
|
2925
|
+
let args2;
|
|
2926
|
+
switch (os) {
|
|
2927
|
+
case "darwin":
|
|
2928
|
+
bin = "open";
|
|
2929
|
+
args2 = [url];
|
|
2930
|
+
break;
|
|
2931
|
+
case "win32":
|
|
2932
|
+
bin = "cmd";
|
|
2933
|
+
args2 = ["/c", "start", "", url];
|
|
2934
|
+
break;
|
|
2935
|
+
default:
|
|
2936
|
+
bin = "xdg-open";
|
|
2937
|
+
args2 = [url];
|
|
2938
|
+
break;
|
|
2939
|
+
}
|
|
2940
|
+
execFile2(bin, args2, () => {
|
|
2941
|
+
});
|
|
2942
|
+
}
|
|
2943
|
+
function sleep(ms) {
|
|
2944
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
2945
|
+
}
|
|
2914
2946
|
function captureClaudeSetupToken() {
|
|
2915
2947
|
return new Promise((resolve2, reject) => {
|
|
2916
2948
|
const proc = nodeSpawn("script", ["-q", "/dev/null", "claude", "setup-token"], {
|
|
@@ -2936,6 +2968,21 @@ function captureClaudeSetupToken() {
|
|
|
2936
2968
|
});
|
|
2937
2969
|
});
|
|
2938
2970
|
}
|
|
2971
|
+
async function apiCall(gatewayUrl, jwt2, path, opts = {}) {
|
|
2972
|
+
const resp = await fetch(`${gatewayUrl}${path}`, {
|
|
2973
|
+
...opts,
|
|
2974
|
+
headers: {
|
|
2975
|
+
"Authorization": `Bearer ${jwt2}`,
|
|
2976
|
+
"Content-Type": "application/json",
|
|
2977
|
+
...opts.headers || {}
|
|
2978
|
+
}
|
|
2979
|
+
});
|
|
2980
|
+
if (!resp.ok) {
|
|
2981
|
+
const text = await resp.text().catch(() => "");
|
|
2982
|
+
throw new Error(`API ${resp.status}: ${text.slice(0, 200)}`);
|
|
2983
|
+
}
|
|
2984
|
+
return resp.json();
|
|
2985
|
+
}
|
|
2939
2986
|
async function setupGithubCommand(opts = {}) {
|
|
2940
2987
|
if (!isAuthenticated()) {
|
|
2941
2988
|
console.error("Not authenticated. Run `synkro-cli login` first.");
|
|
@@ -2951,20 +2998,12 @@ async function setupGithubCommand(opts = {}) {
|
|
|
2951
2998
|
console.log("Requesting CI API key from Synkro...");
|
|
2952
2999
|
let synkroCiApiKey;
|
|
2953
3000
|
try {
|
|
2954
|
-
const
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
body: "{}"
|
|
2961
|
-
});
|
|
2962
|
-
if (!resp.ok) {
|
|
2963
|
-
const errText = await resp.text().catch(() => "");
|
|
2964
|
-
console.error(`Failed to mint CI API key (${resp.status}): ${errText.slice(0, 200)}`);
|
|
2965
|
-
process.exit(1);
|
|
2966
|
-
}
|
|
2967
|
-
const minted = await resp.json();
|
|
3001
|
+
const minted = await apiCall(
|
|
3002
|
+
gatewayUrl,
|
|
3003
|
+
jwt2,
|
|
3004
|
+
"/api/v1/cli/ci-api-key",
|
|
3005
|
+
{ method: "POST", body: "{}" }
|
|
3006
|
+
);
|
|
2968
3007
|
synkroCiApiKey = minted.api_key;
|
|
2969
3008
|
console.log(` \u2713 Issued CI key (${synkroCiApiKey.slice(0, 18)}\u2026), expires ${minted.expires_at.slice(0, 10)}`);
|
|
2970
3009
|
} catch (err) {
|
|
@@ -2976,23 +3015,78 @@ async function setupGithubCommand(opts = {}) {
|
|
|
2976
3015
|
ghToken = opts.githubToken;
|
|
2977
3016
|
} else if (opts.nonInteractive) {
|
|
2978
3017
|
try {
|
|
2979
|
-
|
|
3018
|
+
const result = await apiCall(
|
|
3019
|
+
gatewayUrl,
|
|
3020
|
+
jwt2,
|
|
3021
|
+
"/api/v1/cli/github-token"
|
|
3022
|
+
);
|
|
3023
|
+
if (result.connected && result.token) {
|
|
3024
|
+
ghToken = result.token;
|
|
3025
|
+
} else {
|
|
3026
|
+
throw new Error("not connected");
|
|
3027
|
+
}
|
|
2980
3028
|
} catch {
|
|
2981
|
-
|
|
2982
|
-
|
|
3029
|
+
try {
|
|
3030
|
+
ghToken = execSync3("gh auth token", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
3031
|
+
} catch {
|
|
3032
|
+
console.error("GitHub not connected. Run `synkro-cli setup-github` interactively to connect.");
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
2983
3035
|
}
|
|
2984
3036
|
} else {
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
3037
|
+
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");
|
|
2996
3090
|
}
|
|
2997
3091
|
}
|
|
2998
3092
|
let claudeToken;
|
|
@@ -3043,10 +3137,10 @@ async function setupGithubCommand(opts = {}) {
|
|
|
3043
3137
|
selected = [{ owner, repo, full_name: currentFullName }];
|
|
3044
3138
|
console.log(` Auto-selected repo: ${currentFullName}`);
|
|
3045
3139
|
} else {
|
|
3046
|
-
console.log("
|
|
3140
|
+
console.log("Fetching accessible repos...");
|
|
3047
3141
|
const repos = await listAccessibleRepos({ token: ghToken });
|
|
3048
3142
|
if (repos.length === 0) {
|
|
3049
|
-
console.error("No accessible repos found.
|
|
3143
|
+
console.error("No accessible repos found. Check your GitHub permissions.");
|
|
3050
3144
|
process.exit(1);
|
|
3051
3145
|
}
|
|
3052
3146
|
console.log(`
|
|
@@ -3238,7 +3332,7 @@ function writeConfigEnv(opts) {
|
|
|
3238
3332
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
3239
3333
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
3240
3334
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
3241
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.3.
|
|
3335
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.3.21")}`
|
|
3242
3336
|
];
|
|
3243
3337
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
3244
3338
|
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|
|
@@ -3258,10 +3352,48 @@ function collectLocalMetadata() {
|
|
|
3258
3352
|
}
|
|
3259
3353
|
try {
|
|
3260
3354
|
const remote = execSync4("git remote get-url origin", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
3261
|
-
const
|
|
3355
|
+
const sshMatch = remote.match(/^git@[^:]+:(.+?)(?:\.git)?$/);
|
|
3356
|
+
const httpMatch = remote.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);
|
|
3357
|
+
const m = sshMatch || httpMatch;
|
|
3262
3358
|
if (m) meta.active_repo = m[1];
|
|
3263
3359
|
} catch {
|
|
3264
3360
|
}
|
|
3361
|
+
try {
|
|
3362
|
+
meta.cc_version = execSync4("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
3363
|
+
} catch {
|
|
3364
|
+
}
|
|
3365
|
+
const claudeDir = join6(homedir5(), ".claude");
|
|
3366
|
+
try {
|
|
3367
|
+
const settings = JSON.parse(readFileSync5(join6(claudeDir, "settings.json"), "utf-8"));
|
|
3368
|
+
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
3369
|
+
if (plugins.length) meta.enabled_plugins = plugins;
|
|
3370
|
+
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
3371
|
+
} catch {
|
|
3372
|
+
}
|
|
3373
|
+
try {
|
|
3374
|
+
const mcpCache = JSON.parse(readFileSync5(join6(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
3375
|
+
const mcpNames = Object.keys(mcpCache);
|
|
3376
|
+
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
3377
|
+
} catch {
|
|
3378
|
+
}
|
|
3379
|
+
try {
|
|
3380
|
+
const mcpList = execSync4("claude mcp list 2>/dev/null", { encoding: "utf-8", timeout: 1e4 });
|
|
3381
|
+
const connected = mcpList.split("\n").filter((l) => l.includes("Connected")).map((l) => l.split(":")[0].trim()).filter(Boolean);
|
|
3382
|
+
if (connected.length) meta.mcp_servers_connected = connected;
|
|
3383
|
+
} catch {
|
|
3384
|
+
}
|
|
3385
|
+
try {
|
|
3386
|
+
const sessionsDir = join6(claudeDir, "sessions");
|
|
3387
|
+
const files = readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
3388
|
+
for (const f of files) {
|
|
3389
|
+
const s = JSON.parse(readFileSync5(join6(sessionsDir, f), "utf-8"));
|
|
3390
|
+
if (s.version) {
|
|
3391
|
+
meta.cc_version = meta.cc_version || s.version;
|
|
3392
|
+
break;
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
} catch {
|
|
3396
|
+
}
|
|
3265
3397
|
return meta;
|
|
3266
3398
|
}
|
|
3267
3399
|
async function fetchUserProfile(gatewayUrl, token) {
|
|
@@ -4343,13 +4475,16 @@ ${hunks}`;
|
|
|
4343
4475
|
});
|
|
4344
4476
|
});
|
|
4345
4477
|
}
|
|
4346
|
-
async function processInBatches(items,
|
|
4347
|
-
const results =
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4478
|
+
async function processInBatches(items, concurrency, fn) {
|
|
4479
|
+
const results = new Array(items.length);
|
|
4480
|
+
let next = 0;
|
|
4481
|
+
async function worker() {
|
|
4482
|
+
while (next < items.length) {
|
|
4483
|
+
const idx = next++;
|
|
4484
|
+
results[idx] = await fn(items[idx], idx, items.length);
|
|
4485
|
+
}
|
|
4352
4486
|
}
|
|
4487
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
4353
4488
|
return results;
|
|
4354
4489
|
}
|
|
4355
4490
|
function buildConsolidationPrompt(findings) {
|
|
@@ -4631,12 +4766,12 @@ async function scanPrCommand() {
|
|
|
4631
4766
|
return;
|
|
4632
4767
|
}
|
|
4633
4768
|
const t0 = Date.now();
|
|
4634
|
-
const results = await processInBatches(eligible, MAX_PARALLEL_FILES, async (file) => {
|
|
4635
|
-
process.stdout.write(`
|
|
4769
|
+
const results = await processInBatches(eligible, MAX_PARALLEL_FILES, async (file, idx, total) => {
|
|
4770
|
+
process.stdout.write(`[${idx + 1}/${total}] ${file.filename}...`);
|
|
4636
4771
|
const literalFindings = applyLiteralMatchNegative(literalNegativeRules, file);
|
|
4637
4772
|
const llmResult = await spawnClaudeJudge(file, claudeToken, promptHeader);
|
|
4638
4773
|
const merged = [...literalFindings, ...llmResult.findings];
|
|
4639
|
-
console.log(` ${merged.length} finding(s) (${
|
|
4774
|
+
console.log(` ${merged.length === 0 ? "clean" : `${merged.length} finding(s)`} (${(llmResult.latencyMs / 1e3).toFixed(1)}s)`);
|
|
4640
4775
|
return { findings: merged, latencyMs: llmResult.latencyMs };
|
|
4641
4776
|
});
|
|
4642
4777
|
const totalLatencyMs = Date.now() - t0;
|
|
@@ -4690,7 +4825,7 @@ var init_scanPr = __esm({
|
|
|
4690
4825
|
/go\.sum$/
|
|
4691
4826
|
];
|
|
4692
4827
|
MAX_DIFF_LINES_PER_FILE = 1e3;
|
|
4693
|
-
MAX_PARALLEL_FILES =
|
|
4828
|
+
MAX_PARALLEL_FILES = 10;
|
|
4694
4829
|
}
|
|
4695
4830
|
});
|
|
4696
4831
|
|