@synkro-sh/cli 1.3.19 → 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 +196 -54
- 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";
|
|
@@ -2652,7 +2661,9 @@ import { createInterface } from "readline";
|
|
|
2652
2661
|
function detectGitRepo() {
|
|
2653
2662
|
try {
|
|
2654
2663
|
const remoteUrl = execSync2("git remote get-url origin", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
2655
|
-
const
|
|
2664
|
+
const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\.git)?$/);
|
|
2665
|
+
const httpMatch = remoteUrl.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);
|
|
2666
|
+
const match = sshMatch || httpMatch;
|
|
2656
2667
|
if (!match) return null;
|
|
2657
2668
|
const fullName = match[1];
|
|
2658
2669
|
return { fullName, shortName: fullName.split("/").pop() || fullName };
|
|
@@ -2719,11 +2730,14 @@ function waitForGithubToken() {
|
|
|
2719
2730
|
});
|
|
2720
2731
|
}
|
|
2721
2732
|
function openBrowser2(url) {
|
|
2722
|
-
const { execFile:
|
|
2733
|
+
const { execFile: execFile3 } = __require("child_process");
|
|
2723
2734
|
const plat = process.platform;
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2735
|
+
const cb = (err) => {
|
|
2736
|
+
if (err) console.log(` Open this URL manually: ${url}`);
|
|
2737
|
+
};
|
|
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);
|
|
2727
2741
|
}
|
|
2728
2742
|
async function connectGithubAndSelectRepos() {
|
|
2729
2743
|
const url = `${SYNKRO_WEB_AUTH_URL2}/cli-github?port=${GITHUB_PORT}`;
|
|
@@ -2863,8 +2877,9 @@ import { createInterface as createInterface2 } from "readline/promises";
|
|
|
2863
2877
|
import { stdin as input, stdout as output } from "process";
|
|
2864
2878
|
import { execSync as execSync3, spawn as nodeSpawn } from "child_process";
|
|
2865
2879
|
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
2866
|
-
import { homedir as homedir4 } from "os";
|
|
2880
|
+
import { homedir as homedir4, platform as platform2 } from "os";
|
|
2867
2881
|
import { join as join5 } from "path";
|
|
2882
|
+
import { execFile as execFile2 } from "child_process";
|
|
2868
2883
|
function readConfig() {
|
|
2869
2884
|
if (!existsSync6(CONFIG_PATH)) return {};
|
|
2870
2885
|
const out = {};
|
|
@@ -2892,9 +2907,7 @@ async function prompt(rl, q, opts = {}) {
|
|
|
2892
2907
|
resolve2(chunk);
|
|
2893
2908
|
return;
|
|
2894
2909
|
}
|
|
2895
|
-
if (s === "")
|
|
2896
|
-
process.exit(130);
|
|
2897
|
-
}
|
|
2910
|
+
if (s === "") process.exit(130);
|
|
2898
2911
|
if (s === "\x7F" || s === "\b") {
|
|
2899
2912
|
chunk = chunk.slice(0, -1);
|
|
2900
2913
|
return;
|
|
@@ -2906,6 +2919,30 @@ async function prompt(rl, q, opts = {}) {
|
|
|
2906
2919
|
}
|
|
2907
2920
|
return await rl.question(q);
|
|
2908
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
|
+
}
|
|
2909
2946
|
function captureClaudeSetupToken() {
|
|
2910
2947
|
return new Promise((resolve2, reject) => {
|
|
2911
2948
|
const proc = nodeSpawn("script", ["-q", "/dev/null", "claude", "setup-token"], {
|
|
@@ -2931,6 +2968,21 @@ function captureClaudeSetupToken() {
|
|
|
2931
2968
|
});
|
|
2932
2969
|
});
|
|
2933
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
|
+
}
|
|
2934
2986
|
async function setupGithubCommand(opts = {}) {
|
|
2935
2987
|
if (!isAuthenticated()) {
|
|
2936
2988
|
console.error("Not authenticated. Run `synkro-cli login` first.");
|
|
@@ -2946,20 +2998,12 @@ async function setupGithubCommand(opts = {}) {
|
|
|
2946
2998
|
console.log("Requesting CI API key from Synkro...");
|
|
2947
2999
|
let synkroCiApiKey;
|
|
2948
3000
|
try {
|
|
2949
|
-
const
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
body: "{}"
|
|
2956
|
-
});
|
|
2957
|
-
if (!resp.ok) {
|
|
2958
|
-
const errText = await resp.text().catch(() => "");
|
|
2959
|
-
console.error(`Failed to mint CI API key (${resp.status}): ${errText.slice(0, 200)}`);
|
|
2960
|
-
process.exit(1);
|
|
2961
|
-
}
|
|
2962
|
-
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
|
+
);
|
|
2963
3007
|
synkroCiApiKey = minted.api_key;
|
|
2964
3008
|
console.log(` \u2713 Issued CI key (${synkroCiApiKey.slice(0, 18)}\u2026), expires ${minted.expires_at.slice(0, 10)}`);
|
|
2965
3009
|
} catch (err) {
|
|
@@ -2971,23 +3015,78 @@ async function setupGithubCommand(opts = {}) {
|
|
|
2971
3015
|
ghToken = opts.githubToken;
|
|
2972
3016
|
} else if (opts.nonInteractive) {
|
|
2973
3017
|
try {
|
|
2974
|
-
|
|
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
|
+
}
|
|
2975
3028
|
} catch {
|
|
2976
|
-
|
|
2977
|
-
|
|
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
|
+
}
|
|
2978
3035
|
}
|
|
2979
3036
|
} else {
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
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");
|
|
2991
3090
|
}
|
|
2992
3091
|
}
|
|
2993
3092
|
let claudeToken;
|
|
@@ -3038,10 +3137,10 @@ async function setupGithubCommand(opts = {}) {
|
|
|
3038
3137
|
selected = [{ owner, repo, full_name: currentFullName }];
|
|
3039
3138
|
console.log(` Auto-selected repo: ${currentFullName}`);
|
|
3040
3139
|
} else {
|
|
3041
|
-
console.log("
|
|
3140
|
+
console.log("Fetching accessible repos...");
|
|
3042
3141
|
const repos = await listAccessibleRepos({ token: ghToken });
|
|
3043
3142
|
if (repos.length === 0) {
|
|
3044
|
-
console.error("No accessible repos found.
|
|
3143
|
+
console.error("No accessible repos found. Check your GitHub permissions.");
|
|
3045
3144
|
process.exit(1);
|
|
3046
3145
|
}
|
|
3047
3146
|
console.log(`
|
|
@@ -3233,7 +3332,7 @@ function writeConfigEnv(opts) {
|
|
|
3233
3332
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
3234
3333
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
3235
3334
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
3236
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.3.
|
|
3335
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.3.21")}`
|
|
3237
3336
|
];
|
|
3238
3337
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
3239
3338
|
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|
|
@@ -3253,10 +3352,48 @@ function collectLocalMetadata() {
|
|
|
3253
3352
|
}
|
|
3254
3353
|
try {
|
|
3255
3354
|
const remote = execSync4("git remote get-url origin", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
3256
|
-
const
|
|
3355
|
+
const sshMatch = remote.match(/^git@[^:]+:(.+?)(?:\.git)?$/);
|
|
3356
|
+
const httpMatch = remote.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);
|
|
3357
|
+
const m = sshMatch || httpMatch;
|
|
3257
3358
|
if (m) meta.active_repo = m[1];
|
|
3258
3359
|
} catch {
|
|
3259
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
|
+
}
|
|
3260
3397
|
return meta;
|
|
3261
3398
|
}
|
|
3262
3399
|
async function fetchUserProfile(gatewayUrl, token) {
|
|
@@ -3546,7 +3683,9 @@ async function installCommand(opts = {}) {
|
|
|
3546
3683
|
function detectGitRepo2() {
|
|
3547
3684
|
try {
|
|
3548
3685
|
const remoteUrl = execSync4("git remote get-url origin", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
3549
|
-
const
|
|
3686
|
+
const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\.git)?$/);
|
|
3687
|
+
const httpMatch = remoteUrl.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);
|
|
3688
|
+
const match = sshMatch || httpMatch;
|
|
3550
3689
|
return match ? match[1] : null;
|
|
3551
3690
|
} catch {
|
|
3552
3691
|
return null;
|
|
@@ -4336,13 +4475,16 @@ ${hunks}`;
|
|
|
4336
4475
|
});
|
|
4337
4476
|
});
|
|
4338
4477
|
}
|
|
4339
|
-
async function processInBatches(items,
|
|
4340
|
-
const results =
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
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
|
+
}
|
|
4345
4486
|
}
|
|
4487
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
4346
4488
|
return results;
|
|
4347
4489
|
}
|
|
4348
4490
|
function buildConsolidationPrompt(findings) {
|
|
@@ -4624,12 +4766,12 @@ async function scanPrCommand() {
|
|
|
4624
4766
|
return;
|
|
4625
4767
|
}
|
|
4626
4768
|
const t0 = Date.now();
|
|
4627
|
-
const results = await processInBatches(eligible, MAX_PARALLEL_FILES, async (file) => {
|
|
4628
|
-
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}...`);
|
|
4629
4771
|
const literalFindings = applyLiteralMatchNegative(literalNegativeRules, file);
|
|
4630
4772
|
const llmResult = await spawnClaudeJudge(file, claudeToken, promptHeader);
|
|
4631
4773
|
const merged = [...literalFindings, ...llmResult.findings];
|
|
4632
|
-
console.log(` ${merged.length} finding(s) (${
|
|
4774
|
+
console.log(` ${merged.length === 0 ? "clean" : `${merged.length} finding(s)`} (${(llmResult.latencyMs / 1e3).toFixed(1)}s)`);
|
|
4633
4775
|
return { findings: merged, latencyMs: llmResult.latencyMs };
|
|
4634
4776
|
});
|
|
4635
4777
|
const totalLatencyMs = Date.now() - t0;
|
|
@@ -4683,7 +4825,7 @@ var init_scanPr = __esm({
|
|
|
4683
4825
|
/go\.sum$/
|
|
4684
4826
|
];
|
|
4685
4827
|
MAX_DIFF_LINES_PER_FILE = 1e3;
|
|
4686
|
-
MAX_PARALLEL_FILES =
|
|
4828
|
+
MAX_PARALLEL_FILES = 10;
|
|
4687
4829
|
}
|
|
4688
4830
|
});
|
|
4689
4831
|
|